ยปCore Development>Code coverage>Lib/idlelib/config.py

Python code coverage for Lib/idlelib/config.py

#countcontent
1n/a"""idlelib.config -- Manage IDLE configuration information.
2n/a
3n/aThe comments at the beginning of config-main.def describe the
4n/aconfiguration files and the design implemented to update user
5n/aconfiguration information. In particular, user configuration choices
6n/awhich duplicate the defaults will be removed from the user's
7n/aconfiguration files, and if a user file becomes empty, it will be
8n/adeleted.
9n/a
10n/aThe configuration database maps options to values. Comceptually, the
11n/adatabase keys are tuples (config-type, section, item). As implemented,
12n/athere are separate dicts for default and user values. Each has
13n/aconfig-type keys 'main', 'extensions', 'highlight', and 'keys'. The
14n/avalue for each key is a ConfigParser instance that maps section and item
15n/ato values. For 'main' and 'extenstons', user values override
16n/adefault values. For 'highlight' and 'keys', user sections augment the
17n/adefault sections (and must, therefore, have distinct names).
18n/a
19n/aThroughout this module there is an emphasis on returning useable defaults
20n/awhen a problem occurs in returning a requested configuration value back to
21n/aidle. This is to allow IDLE to continue to function in spite of errors in
22n/athe retrieval of config information. When a default is returned instead of
23n/aa requested config value, a message is printed to stderr to aid in
24n/aconfiguration problem notification and resolution.
25n/a"""
26n/a# TODOs added Oct 2014, tjr
27n/a
28n/afrom configparser import ConfigParser
29n/aimport os
30n/aimport sys
31n/a
32n/afrom tkinter.font import Font, nametofont
33n/a
34n/aclass InvalidConfigType(Exception): pass
35n/aclass InvalidConfigSet(Exception): pass
36n/aclass InvalidFgBg(Exception): pass
37n/aclass InvalidTheme(Exception): pass
38n/a
39n/aclass IdleConfParser(ConfigParser):
40n/a """
41n/a A ConfigParser specialised for idle configuration file handling
42n/a """
43n/a def __init__(self, cfgFile, cfgDefaults=None):
44n/a """
45n/a cfgFile - string, fully specified configuration file name
46n/a """
47n/a self.file = cfgFile
48n/a ConfigParser.__init__(self, defaults=cfgDefaults, strict=False)
49n/a
50n/a def Get(self, section, option, type=None, default=None, raw=False):
51n/a """
52n/a Get an option value for given section/option or return default.
53n/a If type is specified, return as type.
54n/a """
55n/a # TODO Use default as fallback, at least if not None
56n/a # Should also print Warning(file, section, option).
57n/a # Currently may raise ValueError
58n/a if not self.has_option(section, option):
59n/a return default
60n/a if type == 'bool':
61n/a return self.getboolean(section, option)
62n/a elif type == 'int':
63n/a return self.getint(section, option)
64n/a else:
65n/a return self.get(section, option, raw=raw)
66n/a
67n/a def GetOptionList(self, section):
68n/a "Return a list of options for given section, else []."
69n/a if self.has_section(section):
70n/a return self.options(section)
71n/a else: #return a default value
72n/a return []
73n/a
74n/a def Load(self):
75n/a "Load the configuration file from disk."
76n/a self.read(self.file)
77n/a
78n/aclass IdleUserConfParser(IdleConfParser):
79n/a """
80n/a IdleConfigParser specialised for user configuration handling.
81n/a """
82n/a
83n/a def AddSection(self, section):
84n/a "If section doesn't exist, add it."
85n/a if not self.has_section(section):
86n/a self.add_section(section)
87n/a
88n/a def RemoveEmptySections(self):
89n/a "Remove any sections that have no options."
90n/a for section in self.sections():
91n/a if not self.GetOptionList(section):
92n/a self.remove_section(section)
93n/a
94n/a def IsEmpty(self):
95n/a "Return True if no sections after removing empty sections."
96n/a self.RemoveEmptySections()
97n/a return not self.sections()
98n/a
99n/a def RemoveOption(self, section, option):
100n/a """Return True if option is removed from section, else False.
101n/a
102n/a False if either section does not exist or did not have option.
103n/a """
104n/a if self.has_section(section):
105n/a return self.remove_option(section, option)
106n/a return False
107n/a
108n/a def SetOption(self, section, option, value):
109n/a """Return True if option is added or changed to value, else False.
110n/a
111n/a Add section if required. False means option already had value.
112n/a """
113n/a if self.has_option(section, option):
114n/a if self.get(section, option) == value:
115n/a return False
116n/a else:
117n/a self.set(section, option, value)
118n/a return True
119n/a else:
120n/a if not self.has_section(section):
121n/a self.add_section(section)
122n/a self.set(section, option, value)
123n/a return True
124n/a
125n/a def RemoveFile(self):
126n/a "Remove user config file self.file from disk if it exists."
127n/a if os.path.exists(self.file):
128n/a os.remove(self.file)
129n/a
130n/a def Save(self):
131n/a """Update user configuration file.
132n/a
133n/a Remove empty sections. If resulting config isn't empty, write the file
134n/a to disk. If config is empty, remove the file from disk if it exists.
135n/a
136n/a """
137n/a if not self.IsEmpty():
138n/a fname = self.file
139n/a try:
140n/a cfgFile = open(fname, 'w')
141n/a except OSError:
142n/a os.unlink(fname)
143n/a cfgFile = open(fname, 'w')
144n/a with cfgFile:
145n/a self.write(cfgFile)
146n/a else:
147n/a self.RemoveFile()
148n/a
149n/aclass IdleConf:
150n/a """Hold config parsers for all idle config files in singleton instance.
151n/a
152n/a Default config files, self.defaultCfg --
153n/a for config_type in self.config_types:
154n/a (idle install dir)/config-{config-type}.def
155n/a
156n/a User config files, self.userCfg --
157n/a for config_type in self.config_types:
158n/a (user home dir)/.idlerc/config-{config-type}.cfg
159n/a """
160n/a def __init__(self):
161n/a self.config_types = ('main', 'extensions', 'highlight', 'keys')
162n/a self.defaultCfg = {}
163n/a self.userCfg = {}
164n/a self.cfg = {} # TODO use to select userCfg vs defaultCfg
165n/a self.CreateConfigHandlers()
166n/a self.LoadCfgFiles()
167n/a
168n/a
169n/a def CreateConfigHandlers(self):
170n/a "Populate default and user config parser dictionaries."
171n/a #build idle install path
172n/a if __name__ != '__main__': # we were imported
173n/a idleDir=os.path.dirname(__file__)
174n/a else: # we were exec'ed (for testing only)
175n/a idleDir=os.path.abspath(sys.path[0])
176n/a userDir=self.GetUserCfgDir()
177n/a
178n/a defCfgFiles = {}
179n/a usrCfgFiles = {}
180n/a # TODO eliminate these temporaries by combining loops
181n/a for cfgType in self.config_types: #build config file names
182n/a defCfgFiles[cfgType] = os.path.join(
183n/a idleDir, 'config-' + cfgType + '.def')
184n/a usrCfgFiles[cfgType] = os.path.join(
185n/a userDir, 'config-' + cfgType + '.cfg')
186n/a for cfgType in self.config_types: #create config parsers
187n/a self.defaultCfg[cfgType] = IdleConfParser(defCfgFiles[cfgType])
188n/a self.userCfg[cfgType] = IdleUserConfParser(usrCfgFiles[cfgType])
189n/a
190n/a def GetUserCfgDir(self):
191n/a """Return a filesystem directory for storing user config files.
192n/a
193n/a Creates it if required.
194n/a """
195n/a cfgDir = '.idlerc'
196n/a userDir = os.path.expanduser('~')
197n/a if userDir != '~': # expanduser() found user home dir
198n/a if not os.path.exists(userDir):
199n/a warn = ('\n Warning: os.path.expanduser("~") points to\n ' +
200n/a userDir + ',\n but the path does not exist.')
201n/a try:
202n/a print(warn, file=sys.stderr)
203n/a except OSError:
204n/a pass
205n/a userDir = '~'
206n/a if userDir == "~": # still no path to home!
207n/a # traditionally IDLE has defaulted to os.getcwd(), is this adequate?
208n/a userDir = os.getcwd()
209n/a userDir = os.path.join(userDir, cfgDir)
210n/a if not os.path.exists(userDir):
211n/a try:
212n/a os.mkdir(userDir)
213n/a except OSError:
214n/a warn = ('\n Warning: unable to create user config directory\n' +
215n/a userDir + '\n Check path and permissions.\n Exiting!\n')
216n/a print(warn, file=sys.stderr)
217n/a raise SystemExit
218n/a # TODO continue without userDIr instead of exit
219n/a return userDir
220n/a
221n/a def GetOption(self, configType, section, option, default=None, type=None,
222n/a warn_on_default=True, raw=False):
223n/a """Return a value for configType section option, or default.
224n/a
225n/a If type is not None, return a value of that type. Also pass raw
226n/a to the config parser. First try to return a valid value
227n/a (including type) from a user configuration. If that fails, try
228n/a the default configuration. If that fails, return default, with a
229n/a default of None.
230n/a
231n/a Warn if either user or default configurations have an invalid value.
232n/a Warn if default is returned and warn_on_default is True.
233n/a """
234n/a try:
235n/a if self.userCfg[configType].has_option(section, option):
236n/a return self.userCfg[configType].Get(section, option,
237n/a type=type, raw=raw)
238n/a except ValueError:
239n/a warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
240n/a ' invalid %r value for configuration option %r\n'
241n/a ' from section %r: %r' %
242n/a (type, option, section,
243n/a self.userCfg[configType].Get(section, option, raw=raw)))
244n/a _warn(warning, configType, section, option)
245n/a try:
246n/a if self.defaultCfg[configType].has_option(section,option):
247n/a return self.defaultCfg[configType].Get(
248n/a section, option, type=type, raw=raw)
249n/a except ValueError:
250n/a pass
251n/a #returning default, print warning
252n/a if warn_on_default:
253n/a warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
254n/a ' problem retrieving configuration option %r\n'
255n/a ' from section %r.\n'
256n/a ' returning default value: %r' %
257n/a (option, section, default))
258n/a _warn(warning, configType, section, option)
259n/a return default
260n/a
261n/a def SetOption(self, configType, section, option, value):
262n/a """Set section option to value in user config file."""
263n/a self.userCfg[configType].SetOption(section, option, value)
264n/a
265n/a def GetSectionList(self, configSet, configType):
266n/a """Return sections for configSet configType configuration.
267n/a
268n/a configSet must be either 'user' or 'default'
269n/a configType must be in self.config_types.
270n/a """
271n/a if not (configType in self.config_types):
272n/a raise InvalidConfigType('Invalid configType specified')
273n/a if configSet == 'user':
274n/a cfgParser = self.userCfg[configType]
275n/a elif configSet == 'default':
276n/a cfgParser=self.defaultCfg[configType]
277n/a else:
278n/a raise InvalidConfigSet('Invalid configSet specified')
279n/a return cfgParser.sections()
280n/a
281n/a def GetHighlight(self, theme, element, fgBg=None):
282n/a """Return individual theme element highlight color(s).
283n/a
284n/a fgBg - string ('fg' or 'bg') or None.
285n/a If None, return a dictionary containing fg and bg colors with
286n/a keys 'foreground' and 'background'. Otherwise, only return
287n/a fg or bg color, as specified. Colors are intended to be
288n/a appropriate for passing to Tkinter in, e.g., a tag_config call).
289n/a """
290n/a if self.defaultCfg['highlight'].has_section(theme):
291n/a themeDict = self.GetThemeDict('default', theme)
292n/a else:
293n/a themeDict = self.GetThemeDict('user', theme)
294n/a fore = themeDict[element + '-foreground']
295n/a if element == 'cursor': # There is no config value for cursor bg
296n/a back = themeDict['normal-background']
297n/a else:
298n/a back = themeDict[element + '-background']
299n/a highlight = {"foreground": fore, "background": back}
300n/a if not fgBg: # Return dict of both colors
301n/a return highlight
302n/a else: # Return specified color only
303n/a if fgBg == 'fg':
304n/a return highlight["foreground"]
305n/a if fgBg == 'bg':
306n/a return highlight["background"]
307n/a else:
308n/a raise InvalidFgBg('Invalid fgBg specified')
309n/a
310n/a def GetThemeDict(self, type, themeName):
311n/a """Return {option:value} dict for elements in themeName.
312n/a
313n/a type - string, 'default' or 'user' theme type
314n/a themeName - string, theme name
315n/a Values are loaded over ultimate fallback defaults to guarantee
316n/a that all theme elements are present in a newly created theme.
317n/a """
318n/a if type == 'user':
319n/a cfgParser = self.userCfg['highlight']
320n/a elif type == 'default':
321n/a cfgParser = self.defaultCfg['highlight']
322n/a else:
323n/a raise InvalidTheme('Invalid theme type specified')
324n/a # Provide foreground and background colors for each theme
325n/a # element (other than cursor) even though some values are not
326n/a # yet used by idle, to allow for their use in the future.
327n/a # Default values are generally black and white.
328n/a # TODO copy theme from a class attribute.
329n/a theme ={'normal-foreground':'#000000',
330n/a 'normal-background':'#ffffff',
331n/a 'keyword-foreground':'#000000',
332n/a 'keyword-background':'#ffffff',
333n/a 'builtin-foreground':'#000000',
334n/a 'builtin-background':'#ffffff',
335n/a 'comment-foreground':'#000000',
336n/a 'comment-background':'#ffffff',
337n/a 'string-foreground':'#000000',
338n/a 'string-background':'#ffffff',
339n/a 'definition-foreground':'#000000',
340n/a 'definition-background':'#ffffff',
341n/a 'hilite-foreground':'#000000',
342n/a 'hilite-background':'gray',
343n/a 'break-foreground':'#ffffff',
344n/a 'break-background':'#000000',
345n/a 'hit-foreground':'#ffffff',
346n/a 'hit-background':'#000000',
347n/a 'error-foreground':'#ffffff',
348n/a 'error-background':'#000000',
349n/a #cursor (only foreground can be set)
350n/a 'cursor-foreground':'#000000',
351n/a #shell window
352n/a 'stdout-foreground':'#000000',
353n/a 'stdout-background':'#ffffff',
354n/a 'stderr-foreground':'#000000',
355n/a 'stderr-background':'#ffffff',
356n/a 'console-foreground':'#000000',
357n/a 'console-background':'#ffffff' }
358n/a for element in theme:
359n/a if not cfgParser.has_option(themeName, element):
360n/a # Print warning that will return a default color
361n/a warning = ('\n Warning: config.IdleConf.GetThemeDict'
362n/a ' -\n problem retrieving theme element %r'
363n/a '\n from theme %r.\n'
364n/a ' returning default color: %r' %
365n/a (element, themeName, theme[element]))
366n/a _warn(warning, 'highlight', themeName, element)
367n/a theme[element] = cfgParser.Get(
368n/a themeName, element, default=theme[element])
369n/a return theme
370n/a
371n/a def CurrentTheme(self):
372n/a "Return the name of the currently active text color theme."
373n/a return self.current_colors_and_keys('Theme')
374n/a
375n/a def CurrentKeys(self):
376n/a """Return the name of the currently active key set."""
377n/a return self.current_colors_and_keys('Keys')
378n/a
379n/a def current_colors_and_keys(self, section):
380n/a """Return the currently active name for Theme or Keys section.
381n/a
382n/a idlelib.config-main.def ('default') includes these sections
383n/a
384n/a [Theme]
385n/a default= 1
386n/a name= IDLE Classic
387n/a name2=
388n/a
389n/a [Keys]
390n/a default= 1
391n/a name=
392n/a name2=
393n/a
394n/a Item 'name2', is used for built-in ('default') themes and keys
395n/a added after 2015 Oct 1 and 2016 July 1. This kludge is needed
396n/a because setting 'name' to a builtin not defined in older IDLEs
397n/a to display multiple error messages or quit.
398n/a See https://bugs.python.org/issue25313.
399n/a When default = True, 'name2' takes precedence over 'name',
400n/a while older IDLEs will just use name. When default = False,
401n/a 'name2' may still be set, but it is ignored.
402n/a """
403n/a cfgname = 'highlight' if section == 'Theme' else 'keys'
404n/a default = self.GetOption('main', section, 'default',
405n/a type='bool', default=True)
406n/a name = ''
407n/a if default:
408n/a name = self.GetOption('main', section, 'name2', default='')
409n/a if not name:
410n/a name = self.GetOption('main', section, 'name', default='')
411n/a if name:
412n/a source = self.defaultCfg if default else self.userCfg
413n/a if source[cfgname].has_section(name):
414n/a return name
415n/a return "IDLE Classic" if section == 'Theme' else self.default_keys()
416n/a
417n/a @staticmethod
418n/a def default_keys():
419n/a if sys.platform[:3] == 'win':
420n/a return 'IDLE Classic Windows'
421n/a elif sys.platform == 'darwin':
422n/a return 'IDLE Classic OSX'
423n/a else:
424n/a return 'IDLE Modern Unix'
425n/a
426n/a def GetExtensions(self, active_only=True,
427n/a editor_only=False, shell_only=False):
428n/a """Return extensions in default and user config-extensions files.
429n/a
430n/a If active_only True, only return active (enabled) extensions
431n/a and optionally only editor or shell extensions.
432n/a If active_only False, return all extensions.
433n/a """
434n/a extns = self.RemoveKeyBindNames(
435n/a self.GetSectionList('default', 'extensions'))
436n/a userExtns = self.RemoveKeyBindNames(
437n/a self.GetSectionList('user', 'extensions'))
438n/a for extn in userExtns:
439n/a if extn not in extns: #user has added own extension
440n/a extns.append(extn)
441n/a if active_only:
442n/a activeExtns = []
443n/a for extn in extns:
444n/a if self.GetOption('extensions', extn, 'enable', default=True,
445n/a type='bool'):
446n/a #the extension is enabled
447n/a if editor_only or shell_only: # TODO both True contradict
448n/a if editor_only:
449n/a option = "enable_editor"
450n/a else:
451n/a option = "enable_shell"
452n/a if self.GetOption('extensions', extn,option,
453n/a default=True, type='bool',
454n/a warn_on_default=False):
455n/a activeExtns.append(extn)
456n/a else:
457n/a activeExtns.append(extn)
458n/a return activeExtns
459n/a else:
460n/a return extns
461n/a
462n/a def RemoveKeyBindNames(self, extnNameList):
463n/a "Return extnNameList with keybinding section names removed."
464n/a # TODO Easier to return filtered copy with list comp
465n/a names = extnNameList
466n/a kbNameIndicies = []
467n/a for name in names:
468n/a if name.endswith(('_bindings', '_cfgBindings')):
469n/a kbNameIndicies.append(names.index(name))
470n/a kbNameIndicies.sort(reverse=True)
471n/a for index in kbNameIndicies: #delete each keybinding section name
472n/a del(names[index])
473n/a return names
474n/a
475n/a def GetExtnNameForEvent(self, virtualEvent):
476n/a """Return the name of the extension binding virtualEvent, or None.
477n/a
478n/a virtualEvent - string, name of the virtual event to test for,
479n/a without the enclosing '<< >>'
480n/a """
481n/a extName = None
482n/a vEvent = '<<' + virtualEvent + '>>'
483n/a for extn in self.GetExtensions(active_only=0):
484n/a for event in self.GetExtensionKeys(extn):
485n/a if event == vEvent:
486n/a extName = extn # TODO return here?
487n/a return extName
488n/a
489n/a def GetExtensionKeys(self, extensionName):
490n/a """Return dict: {configurable extensionName event : active keybinding}.
491n/a
492n/a Events come from default config extension_cfgBindings section.
493n/a Keybindings come from GetCurrentKeySet() active key dict,
494n/a where previously used bindings are disabled.
495n/a """
496n/a keysName = extensionName + '_cfgBindings'
497n/a activeKeys = self.GetCurrentKeySet()
498n/a extKeys = {}
499n/a if self.defaultCfg['extensions'].has_section(keysName):
500n/a eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
501n/a for eventName in eventNames:
502n/a event = '<<' + eventName + '>>'
503n/a binding = activeKeys[event]
504n/a extKeys[event] = binding
505n/a return extKeys
506n/a
507n/a def __GetRawExtensionKeys(self,extensionName):
508n/a """Return dict {configurable extensionName event : keybinding list}.
509n/a
510n/a Events come from default config extension_cfgBindings section.
511n/a Keybindings list come from the splitting of GetOption, which
512n/a tries user config before default config.
513n/a """
514n/a keysName = extensionName+'_cfgBindings'
515n/a extKeys = {}
516n/a if self.defaultCfg['extensions'].has_section(keysName):
517n/a eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
518n/a for eventName in eventNames:
519n/a binding = self.GetOption(
520n/a 'extensions', keysName, eventName, default='').split()
521n/a event = '<<' + eventName + '>>'
522n/a extKeys[event] = binding
523n/a return extKeys
524n/a
525n/a def GetExtensionBindings(self, extensionName):
526n/a """Return dict {extensionName event : active or defined keybinding}.
527n/a
528n/a Augment self.GetExtensionKeys(extensionName) with mapping of non-
529n/a configurable events (from default config) to GetOption splits,
530n/a as in self.__GetRawExtensionKeys.
531n/a """
532n/a bindsName = extensionName + '_bindings'
533n/a extBinds = self.GetExtensionKeys(extensionName)
534n/a #add the non-configurable bindings
535n/a if self.defaultCfg['extensions'].has_section(bindsName):
536n/a eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName)
537n/a for eventName in eventNames:
538n/a binding = self.GetOption(
539n/a 'extensions', bindsName, eventName, default='').split()
540n/a event = '<<' + eventName + '>>'
541n/a extBinds[event] = binding
542n/a
543n/a return extBinds
544n/a
545n/a def GetKeyBinding(self, keySetName, eventStr):
546n/a """Return the keybinding list for keySetName eventStr.
547n/a
548n/a keySetName - name of key binding set (config-keys section).
549n/a eventStr - virtual event, including brackets, as in '<<event>>'.
550n/a """
551n/a eventName = eventStr[2:-2] #trim off the angle brackets
552n/a binding = self.GetOption('keys', keySetName, eventName, default='',
553n/a warn_on_default=False).split()
554n/a return binding
555n/a
556n/a def GetCurrentKeySet(self):
557n/a "Return CurrentKeys with 'darwin' modifications."
558n/a result = self.GetKeySet(self.CurrentKeys())
559n/a
560n/a if sys.platform == "darwin":
561n/a # OS X Tk variants do not support the "Alt" keyboard modifier.
562n/a # So replace all keybingings that use "Alt" with ones that
563n/a # use the "Option" keyboard modifier.
564n/a # TODO (Ned?): the "Option" modifier does not work properly for
565n/a # Cocoa Tk and XQuartz Tk so we should not use it
566n/a # in default OS X KeySets.
567n/a for k, v in result.items():
568n/a v2 = [ x.replace('<Alt-', '<Option-') for x in v ]
569n/a if v != v2:
570n/a result[k] = v2
571n/a
572n/a return result
573n/a
574n/a def GetKeySet(self, keySetName):
575n/a """Return event-key dict for keySetName core plus active extensions.
576n/a
577n/a If a binding defined in an extension is already in use, the
578n/a extension binding is disabled by being set to ''
579n/a """
580n/a keySet = self.GetCoreKeys(keySetName)
581n/a activeExtns = self.GetExtensions(active_only=1)
582n/a for extn in activeExtns:
583n/a extKeys = self.__GetRawExtensionKeys(extn)
584n/a if extKeys: #the extension defines keybindings
585n/a for event in extKeys:
586n/a if extKeys[event] in keySet.values():
587n/a #the binding is already in use
588n/a extKeys[event] = '' #disable this binding
589n/a keySet[event] = extKeys[event] #add binding
590n/a return keySet
591n/a
592n/a def IsCoreBinding(self, virtualEvent):
593n/a """Return True if the virtual event is one of the core idle key events.
594n/a
595n/a virtualEvent - string, name of the virtual event to test for,
596n/a without the enclosing '<< >>'
597n/a """
598n/a return ('<<'+virtualEvent+'>>') in self.GetCoreKeys()
599n/a
600n/a# TODO make keyBindins a file or class attribute used for test above
601n/a# and copied in function below
602n/a
603n/a def GetCoreKeys(self, keySetName=None):
604n/a """Return dict of core virtual-key keybindings for keySetName.
605n/a
606n/a The default keySetName None corresponds to the keyBindings base
607n/a dict. If keySetName is not None, bindings from the config
608n/a file(s) are loaded _over_ these defaults, so if there is a
609n/a problem getting any core binding there will be an 'ultimate last
610n/a resort fallback' to the CUA-ish bindings defined here.
611n/a """
612n/a keyBindings={
613n/a '<<copy>>': ['<Control-c>', '<Control-C>'],
614n/a '<<cut>>': ['<Control-x>', '<Control-X>'],
615n/a '<<paste>>': ['<Control-v>', '<Control-V>'],
616n/a '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
617n/a '<<center-insert>>': ['<Control-l>'],
618n/a '<<close-all-windows>>': ['<Control-q>'],
619n/a '<<close-window>>': ['<Alt-F4>'],
620n/a '<<do-nothing>>': ['<Control-x>'],
621n/a '<<end-of-file>>': ['<Control-d>'],
622n/a '<<python-docs>>': ['<F1>'],
623n/a '<<python-context-help>>': ['<Shift-F1>'],
624n/a '<<history-next>>': ['<Alt-n>'],
625n/a '<<history-previous>>': ['<Alt-p>'],
626n/a '<<interrupt-execution>>': ['<Control-c>'],
627n/a '<<view-restart>>': ['<F6>'],
628n/a '<<restart-shell>>': ['<Control-F6>'],
629n/a '<<open-class-browser>>': ['<Alt-c>'],
630n/a '<<open-module>>': ['<Alt-m>'],
631n/a '<<open-new-window>>': ['<Control-n>'],
632n/a '<<open-window-from-file>>': ['<Control-o>'],
633n/a '<<plain-newline-and-indent>>': ['<Control-j>'],
634n/a '<<print-window>>': ['<Control-p>'],
635n/a '<<redo>>': ['<Control-y>'],
636n/a '<<remove-selection>>': ['<Escape>'],
637n/a '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
638n/a '<<save-window-as-file>>': ['<Alt-s>'],
639n/a '<<save-window>>': ['<Control-s>'],
640n/a '<<select-all>>': ['<Alt-a>'],
641n/a '<<toggle-auto-coloring>>': ['<Control-slash>'],
642n/a '<<undo>>': ['<Control-z>'],
643n/a '<<find-again>>': ['<Control-g>', '<F3>'],
644n/a '<<find-in-files>>': ['<Alt-F3>'],
645n/a '<<find-selection>>': ['<Control-F3>'],
646n/a '<<find>>': ['<Control-f>'],
647n/a '<<replace>>': ['<Control-h>'],
648n/a '<<goto-line>>': ['<Alt-g>'],
649n/a '<<smart-backspace>>': ['<Key-BackSpace>'],
650n/a '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
651n/a '<<smart-indent>>': ['<Key-Tab>'],
652n/a '<<indent-region>>': ['<Control-Key-bracketright>'],
653n/a '<<dedent-region>>': ['<Control-Key-bracketleft>'],
654n/a '<<comment-region>>': ['<Alt-Key-3>'],
655n/a '<<uncomment-region>>': ['<Alt-Key-4>'],
656n/a '<<tabify-region>>': ['<Alt-Key-5>'],
657n/a '<<untabify-region>>': ['<Alt-Key-6>'],
658n/a '<<toggle-tabs>>': ['<Alt-Key-t>'],
659n/a '<<change-indentwidth>>': ['<Alt-Key-u>'],
660n/a '<<del-word-left>>': ['<Control-Key-BackSpace>'],
661n/a '<<del-word-right>>': ['<Control-Key-Delete>']
662n/a }
663n/a if keySetName:
664n/a if not (self.userCfg['keys'].has_section(keySetName) or
665n/a self.defaultCfg['keys'].has_section(keySetName)):
666n/a warning = (
667n/a '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
668n/a ' key set %r is not defined, using default bindings.' %
669n/a (keySetName,)
670n/a )
671n/a _warn(warning, 'keys', keySetName)
672n/a else:
673n/a for event in keyBindings:
674n/a binding = self.GetKeyBinding(keySetName, event)
675n/a if binding:
676n/a keyBindings[event] = binding
677n/a else: #we are going to return a default, print warning
678n/a warning = (
679n/a '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
680n/a ' problem retrieving key binding for event %r\n'
681n/a ' from key set %r.\n'
682n/a ' returning default value: %r' %
683n/a (event, keySetName, keyBindings[event])
684n/a )
685n/a _warn(warning, 'keys', keySetName, event)
686n/a return keyBindings
687n/a
688n/a def GetExtraHelpSourceList(self, configSet):
689n/a """Return list of extra help sources from a given configSet.
690n/a
691n/a Valid configSets are 'user' or 'default'. Return a list of tuples of
692n/a the form (menu_item , path_to_help_file , option), or return the empty
693n/a list. 'option' is the sequence number of the help resource. 'option'
694n/a values determine the position of the menu items on the Help menu,
695n/a therefore the returned list must be sorted by 'option'.
696n/a
697n/a """
698n/a helpSources = []
699n/a if configSet == 'user':
700n/a cfgParser = self.userCfg['main']
701n/a elif configSet == 'default':
702n/a cfgParser = self.defaultCfg['main']
703n/a else:
704n/a raise InvalidConfigSet('Invalid configSet specified')
705n/a options=cfgParser.GetOptionList('HelpFiles')
706n/a for option in options:
707n/a value=cfgParser.Get('HelpFiles', option, default=';')
708n/a if value.find(';') == -1: #malformed config entry with no ';'
709n/a menuItem = '' #make these empty
710n/a helpPath = '' #so value won't be added to list
711n/a else: #config entry contains ';' as expected
712n/a value=value.split(';')
713n/a menuItem=value[0].strip()
714n/a helpPath=value[1].strip()
715n/a if menuItem and helpPath: #neither are empty strings
716n/a helpSources.append( (menuItem,helpPath,option) )
717n/a helpSources.sort(key=lambda x: x[2])
718n/a return helpSources
719n/a
720n/a def GetAllExtraHelpSourcesList(self):
721n/a """Return a list of the details of all additional help sources.
722n/a
723n/a Tuples in the list are those of GetExtraHelpSourceList.
724n/a """
725n/a allHelpSources = (self.GetExtraHelpSourceList('default') +
726n/a self.GetExtraHelpSourceList('user') )
727n/a return allHelpSources
728n/a
729n/a def GetFont(self, root, configType, section):
730n/a """Retrieve a font from configuration (font, font-size, font-bold)
731n/a Intercept the special value 'TkFixedFont' and substitute
732n/a the actual font, factoring in some tweaks if needed for
733n/a appearance sakes.
734n/a
735n/a The 'root' parameter can normally be any valid Tkinter widget.
736n/a
737n/a Return a tuple (family, size, weight) suitable for passing
738n/a to tkinter.Font
739n/a """
740n/a family = self.GetOption(configType, section, 'font', default='courier')
741n/a size = self.GetOption(configType, section, 'font-size', type='int',
742n/a default='10')
743n/a bold = self.GetOption(configType, section, 'font-bold', default=0,
744n/a type='bool')
745n/a if (family == 'TkFixedFont'):
746n/a f = Font(name='TkFixedFont', exists=True, root=root)
747n/a actualFont = Font.actual(f)
748n/a family = actualFont['family']
749n/a size = actualFont['size']
750n/a if size <= 0:
751n/a size = 10 # if font in pixels, ignore actual size
752n/a bold = actualFont['weight'] == 'bold'
753n/a return (family, size, 'bold' if bold else 'normal')
754n/a
755n/a def LoadCfgFiles(self):
756n/a "Load all configuration files."
757n/a for key in self.defaultCfg:
758n/a self.defaultCfg[key].Load()
759n/a self.userCfg[key].Load() #same keys
760n/a
761n/a def SaveUserCfgFiles(self):
762n/a "Write all loaded user configuration files to disk."
763n/a for key in self.userCfg:
764n/a self.userCfg[key].Save()
765n/a
766n/a
767n/aidleConf = IdleConf()
768n/a
769n/a
770n/a_warned = set()
771n/adef _warn(msg, *key):
772n/a key = (msg,) + key
773n/a if key not in _warned:
774n/a try:
775n/a print(msg, file=sys.stderr)
776n/a except OSError:
777n/a pass
778n/a _warned.add(key)
779n/a
780n/a
781n/a# TODO Revise test output, write expanded unittest
782n/a#
783n/aif __name__ == '__main__':
784n/a from zlib import crc32
785n/a line, crc = 0, 0
786n/a
787n/a def sprint(obj):
788n/a global line, crc
789n/a txt = str(obj)
790n/a line += 1
791n/a crc = crc32(txt.encode(encoding='utf-8'), crc)
792n/a print(txt)
793n/a #print('***', line, crc, '***') # uncomment for diagnosis
794n/a
795n/a def dumpCfg(cfg):
796n/a print('\n', cfg, '\n') # has variable '0xnnnnnnnn' addresses
797n/a for key in sorted(cfg.keys()):
798n/a sections = cfg[key].sections()
799n/a sprint(key)
800n/a sprint(sections)
801n/a for section in sections:
802n/a options = cfg[key].options(section)
803n/a sprint(section)
804n/a sprint(options)
805n/a for option in options:
806n/a sprint(option + ' = ' + cfg[key].Get(section, option))
807n/a
808n/a dumpCfg(idleConf.defaultCfg)
809n/a dumpCfg(idleConf.userCfg)
810n/a print('\nlines = ', line, ', crc = ', crc, sep='')