Skip to content

Commit edc0342

Browse files
authored
[3.6] bpo-30779: IDLE -- Factor ConfigChanges class from configdialog, put in config; test. (GH-2612) (#2625)
* In config, put dump test code in a function; run it and unittest in 'if __name__ == '__main__'. * Add class config.ConfigChanges based on changes_class_v4.py on bpo issue. * Add class test_config.ChangesTest, partly based on configdialog_tests_v1.py on bpo issue. * Revise configdialog to use ConfigChanges, mostly as specified in tracker msg297804. * Revise test_configdialog to match configdialog changes. All tests pass in both files. * Remove configdialog functions unused or moved to ConfigChanges. Cheryl Sabella contributed parts of the patch. (cherry picked from commit 349abd9)
1 parent 05b72ed commit edc0342

File tree

4 files changed

+277
-171
lines changed

4 files changed

+277
-171
lines changed

Lib/idlelib/config.py

Lines changed: 119 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def __init__(self, cfgFile, cfgDefaults=None):
4444
"""
4545
cfgFile - string, fully specified configuration file name
4646
"""
47-
self.file = cfgFile
47+
self.file = cfgFile # This is currently '' when testing.
4848
ConfigParser.__init__(self, defaults=cfgDefaults, strict=False)
4949

5050
def Get(self, section, option, type=None, default=None, raw=False):
@@ -73,7 +73,8 @@ def GetOptionList(self, section):
7373

7474
def Load(self):
7575
"Load the configuration file from disk."
76-
self.read(self.file)
76+
if self.file:
77+
self.read(self.file)
7778

7879
class IdleUserConfParser(IdleConfParser):
7980
"""
@@ -130,21 +131,22 @@ def RemoveFile(self):
130131
def Save(self):
131132
"""Update user configuration file.
132133
133-
Remove empty sections. If resulting config isn't empty, write the file
134-
to disk. If config is empty, remove the file from disk if it exists.
134+
If self not empty after removing empty sections, write the file
135+
to disk. Otherwise, remove the file from disk if it exists.
135136
136137
"""
137-
if not self.IsEmpty():
138-
fname = self.file
139-
try:
140-
cfgFile = open(fname, 'w')
141-
except OSError:
142-
os.unlink(fname)
143-
cfgFile = open(fname, 'w')
144-
with cfgFile:
145-
self.write(cfgFile)
146-
else:
147-
self.RemoveFile()
138+
fname = self.file
139+
if fname:
140+
if not self.IsEmpty():
141+
try:
142+
cfgFile = open(fname, 'w')
143+
except OSError:
144+
os.unlink(fname)
145+
cfgFile = open(fname, 'w')
146+
with cfgFile:
147+
self.write(cfgFile)
148+
else:
149+
self.RemoveFile()
148150

149151
class IdleConf:
150152
"""Hold config parsers for all idle config files in singleton instance.
@@ -158,7 +160,7 @@ class IdleConf:
158160
(user home dir)/.idlerc/config-{config-type}.cfg
159161
"""
160162
def __init__(self):
161-
self.config_types = ('main', 'extensions', 'highlight', 'keys')
163+
self.config_types = ('main', 'highlight', 'keys', 'extensions')
162164
self.defaultCfg = {}
163165
self.userCfg = {}
164166
self.cfg = {} # TODO use to select userCfg vs defaultCfg
@@ -766,7 +768,6 @@ def SaveUserCfgFiles(self):
766768

767769
idleConf = IdleConf()
768770

769-
770771
_warned = set()
771772
def _warn(msg, *key):
772773
key = (msg,) + key
@@ -778,9 +779,100 @@ def _warn(msg, *key):
778779
_warned.add(key)
779780

780781

782+
class ConfigChanges(dict):
783+
"""Manage a user's proposed configuration option changes.
784+
785+
Names used across multiple methods:
786+
page -- one of the 4 top-level dicts representing a
787+
.idlerc/config-x.cfg file.
788+
config_type -- name of a page.
789+
section -- a section within a page/file.
790+
option -- name of an option within a section.
791+
value -- value for the option.
792+
793+
Methods
794+
add_option: Add option and value to changes.
795+
save_option: Save option and value to config parser.
796+
save_all: Save all the changes to the config parser and file.
797+
delete_section: Delete section if it exists.
798+
clear: Clear all changes by clearing each page.
799+
"""
800+
def __init__(self):
801+
"Create a page for each configuration file"
802+
self.pages = [] # List of unhashable dicts.
803+
for config_type in idleConf.config_types:
804+
self[config_type] = {}
805+
self.pages.append(self[config_type])
806+
807+
def add_option(self, config_type, section, item, value):
808+
"Add item/value pair for config_type and section."
809+
page = self[config_type]
810+
value = str(value) # Make sure we use a string.
811+
if section not in page:
812+
page[section] = {}
813+
page[section][item] = value
814+
815+
@staticmethod
816+
def save_option(config_type, section, item, value):
817+
"""Return True if the configuration value was added or changed.
818+
819+
Helper for save_all.
820+
"""
821+
if idleConf.defaultCfg[config_type].has_option(section, item):
822+
if idleConf.defaultCfg[config_type].Get(section, item) == value:
823+
# The setting equals a default setting, remove it from user cfg.
824+
return idleConf.userCfg[config_type].RemoveOption(section, item)
825+
# If we got here, set the option.
826+
return idleConf.userCfg[config_type].SetOption(section, item, value)
827+
828+
def save_all(self):
829+
"""Save configuration changes to the user config file.
830+
831+
Then clear self in preparation for additional changes.
832+
"""
833+
idleConf.userCfg['main'].Save()
834+
for config_type in self:
835+
cfg_type_changed = False
836+
page = self[config_type]
837+
for section in page:
838+
if section == 'HelpFiles': # Remove it for replacement.
839+
idleConf.userCfg['main'].remove_section('HelpFiles')
840+
cfg_type_changed = True
841+
for item, value in page[section].items():
842+
if self.save_option(config_type, section, item, value):
843+
cfg_type_changed = True
844+
if cfg_type_changed:
845+
idleConf.userCfg[config_type].Save()
846+
for config_type in ['keys', 'highlight']:
847+
# Save these even if unchanged!
848+
idleConf.userCfg[config_type].Save()
849+
self.clear()
850+
# ConfigDialog caller must add the following call
851+
# self.save_all_changed_extensions() # Uses a different mechanism.
852+
853+
def delete_section(self, config_type, section):
854+
"""Delete a section from self, userCfg, and file.
855+
856+
Used to delete custom themes and keysets.
857+
"""
858+
if section in self[config_type]:
859+
del self[config_type][section]
860+
configpage = idleConf.userCfg[config_type]
861+
configpage.remove_section(section)
862+
configpage.Save()
863+
864+
def clear(self):
865+
"""Clear all 4 pages.
866+
867+
Called in save_all after saving to idleConf.
868+
XXX Mark window *title* when there are changes; unmark here.
869+
"""
870+
for page in self.pages:
871+
page.clear()
872+
873+
781874
# TODO Revise test output, write expanded unittest
782-
#
783-
if __name__ == '__main__':
875+
def _dump(): # htest # (not really, but ignore in coverage)
784876
from zlib import crc32
785877
line, crc = 0, 0
786878

@@ -790,10 +882,10 @@ def sprint(obj):
790882
line += 1
791883
crc = crc32(txt.encode(encoding='utf-8'), crc)
792884
print(txt)
793-
#print('***', line, crc, '***') # uncomment for diagnosis
885+
#print('***', line, crc, '***') # Uncomment for diagnosis.
794886

795887
def dumpCfg(cfg):
796-
print('\n', cfg, '\n') # has variable '0xnnnnnnnn' addresses
888+
print('\n', cfg, '\n') # Cfg has variable '0xnnnnnnnn' address.
797889
for key in sorted(cfg.keys()):
798890
sections = cfg[key].sections()
799891
sprint(key)
@@ -808,3 +900,9 @@ def dumpCfg(cfg):
808900
dumpCfg(idleConf.defaultCfg)
809901
dumpCfg(idleConf.userCfg)
810902
print('\nlines = ', line, ', crc = ', crc, sep='')
903+
904+
if __name__ == '__main__':
905+
import unittest
906+
unittest.main('idlelib.idle_test.test_config',
907+
verbosity=2, exit=False)
908+
#_dump()

0 commit comments

Comments
 (0)