diff options
-rw-r--r-- | Lib/idlelib/README.txt | 6 | ||||
-rw-r--r-- | Lib/idlelib/editor.py | 58 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/htest.py | 5 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_query.py | 110 | ||||
-rw-r--r-- | Lib/idlelib/query.py | 83 |
5 files changed, 185 insertions, 77 deletions
diff --git a/Lib/idlelib/README.txt b/Lib/idlelib/README.txt index e1d682e..d333b47 100644 --- a/Lib/idlelib/README.txt +++ b/Lib/idlelib/README.txt @@ -41,7 +41,6 @@ config.py # Load, fetch, and save configuration (nim). configdialog.py # Display user configuration dialogs. config_help.py # Specify help source in configdialog. config_key.py # Change keybindings. -config_sec.py # Spefify user config section name dynoption.py # Define mutable OptionMenu widget (nim). debugobj.py # Define class used in stackviewer. debugobj_r.py # Communicate objects between processes with rpc (nim). @@ -66,6 +65,7 @@ pathbrowser.py # Create path browser window. percolator.py # Manage delegator stack (nim). pyparse.py # Give information on code indentation pyshell.py # Start IDLE, manage shell, complete editor window +query.py # Query user for informtion redirector.py # Intercept widget subcommands (for percolator) (nim). replace.py # Search and replace pattern in text. rpc.py # Commuicate between idle and user processes (nim). @@ -192,8 +192,8 @@ Options Configure IDLE # eEW.config_dialog, configdialog (tabs in the dialog) Font tab # config-main.def - Highlight tab # config_sec, config-highlight.def - Keys tab # config_key, configconfig_secg-keus.def + Highlight tab # query, config-highlight.def + Keys tab # query, config_key, config_keys.def General tab # config_help, config-main.def Extensions tab # config-extensions.def, corresponding .py --- diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 07a1181..7372ecf 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -14,6 +14,7 @@ import traceback import webbrowser from idlelib.multicall import MultiCallCreator +from idlelib import query from idlelib import windows from idlelib import search from idlelib import grep @@ -573,46 +574,27 @@ class EditorWindow(object): text.see("insert") def open_module(self, event=None): - # XXX Shouldn't this be in IOBinding? + """Get module name from user and open it. + + Return module path or None for calls by open_class_browser + when latter is not invoked in named editor window. + """ + # XXX This, open_class_browser, and open_path_browser + # would fit better in iomenu.IOBinding. try: - name = self.text.get("sel.first", "sel.last") + name = self.text.get("sel.first", "sel.last").strip() except TclError: - name = "" - else: - name = name.strip() - name = tkSimpleDialog.askstring("Module", - "Enter the name of a Python module\n" - "to search on sys.path and open:", - parent=self.text, initialvalue=name) - if name: - name = name.strip() - if not name: - return - # XXX Ought to insert current file's directory in front of path - try: - spec = importlib.util.find_spec(name) - except (ValueError, ImportError) as msg: - tkMessageBox.showerror("Import error", str(msg), parent=self.text) - return - if spec is None: - tkMessageBox.showerror("Import error", "module not found", - parent=self.text) - return - if not isinstance(spec.loader, importlib.abc.SourceLoader): - tkMessageBox.showerror("Import error", "not a source-based module", - parent=self.text) - return - try: - file_path = spec.loader.get_filename(name) - except AttributeError: - tkMessageBox.showerror("Import error", - "loader does not support get_filename", - parent=self.text) - return - if self.flist: - self.flist.open(file_path) - else: - self.io.loadfile(file_path) + name = '' + file_path = query.ModuleName( + self.text, "Open Module", + "Enter the name of a Python module\n" + "to search on sys.path and open:", + name).result + if file_path is not None: + if self.flist: + self.flist.open(file_path) + else: + self.io.loadfile(file_path) return file_path def open_class_browser(self, event=None): diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py index d809d30..71302d0 100644 --- a/Lib/idlelib/idle_test/htest.py +++ b/Lib/idlelib/idle_test/htest.py @@ -235,8 +235,9 @@ _percolator_spec = { Query_spec = { 'file': 'query', - 'kwds': {'title':'Query', - 'message':'Enter something', + 'kwds': {'title': 'Query', + 'message': 'Enter something', + 'text0': 'Go', '_htest': True}, 'msg': "Enter with <Return> or [Ok]. Print valid entry to Shell\n" "Blank line, after stripping, is ignored\n" diff --git a/Lib/idlelib/idle_test/test_query.py b/Lib/idlelib/idle_test/test_query.py index e9c4bd4..58873c4 100644 --- a/Lib/idlelib/idle_test/test_query.py +++ b/Lib/idlelib/idle_test/test_query.py @@ -8,8 +8,8 @@ import unittest from unittest import mock from idlelib.idle_test.mock_tk import Var, Mbox_func from idlelib import query -Query, SectionName = query.Query, query.SectionName +Query = query.Query class Dummy_Query: # Mock for testing the following methods Query entry_ok = Query.entry_ok @@ -23,7 +23,7 @@ class Dummy_Query: self.destroyed = True # entry_ok calls modal messagebox.showerror if entry is not ok. -# Mock showerrer returns, so don't need to click to continue. +# Mock showerrer so don't need to click to continue. orig_showerror = query.showerror showerror = Mbox_func() # Instance has __call__ method. @@ -46,7 +46,7 @@ class QueryTest(unittest.TestCase): dialog = self.dialog Equal = self.assertEqual dialog.entry.set(' ') - Equal(dialog.entry_ok(), '') + Equal(dialog.entry_ok(), None) Equal((dialog.result, dialog.destroyed), (None, False)) Equal(showerror.title, 'Entry Error') self.assertIn('Blank', showerror.message) @@ -74,44 +74,41 @@ class QueryTest(unittest.TestCase): class Dummy_SectionName: - # Mock for testing the following method of Section_Name - entry_ok = SectionName.entry_ok - # Attributes, constant or variable, needed for tests + entry_ok = query.SectionName.entry_ok # Test override. used_names = ['used'] entry = Var() class SectionNameTest(unittest.TestCase): dialog = Dummy_SectionName() - def setUp(self): showerror.title = None - def test_blank_name(self): + def test_blank_section_name(self): dialog = self.dialog Equal = self.assertEqual dialog.entry.set(' ') - Equal(dialog.entry_ok(), '') + Equal(dialog.entry_ok(), None) Equal(showerror.title, 'Name Error') self.assertIn('No', showerror.message) - def test_used_name(self): + def test_used_section_name(self): dialog = self.dialog Equal = self.assertEqual dialog.entry.set('used') - Equal(self.dialog.entry_ok(), '') + Equal(self.dialog.entry_ok(), None) Equal(showerror.title, 'Name Error') self.assertIn('use', showerror.message) - def test_long_name(self): + def test_long_section_name(self): dialog = self.dialog Equal = self.assertEqual dialog.entry.set('good'*8) - Equal(self.dialog.entry_ok(), '') + Equal(self.dialog.entry_ok(), None) Equal(showerror.title, 'Name Error') self.assertIn('too long', showerror.message) - def test_good_entry(self): + def test_good_section_name(self): dialog = self.dialog Equal = self.assertEqual dialog.entry.set(' good ') @@ -119,13 +116,56 @@ class SectionNameTest(unittest.TestCase): Equal(showerror.title, None) +class Dummy_ModuleName: + entry_ok = query.ModuleName.entry_ok # Test override + text0 = '' + entry = Var() + +class ModuleNameTest(unittest.TestCase): + dialog = Dummy_ModuleName() + + def setUp(self): + showerror.title = None + + def test_blank_module_name(self): + dialog = self.dialog + Equal = self.assertEqual + dialog.entry.set(' ') + Equal(dialog.entry_ok(), None) + Equal(showerror.title, 'Name Error') + self.assertIn('No', showerror.message) + + def test_bogus_module_name(self): + dialog = self.dialog + Equal = self.assertEqual + dialog.entry.set('__name_xyz123_should_not_exist__') + Equal(self.dialog.entry_ok(), None) + Equal(showerror.title, 'Import Error') + self.assertIn('not found', showerror.message) + + def test_c_source_name(self): + dialog = self.dialog + Equal = self.assertEqual + dialog.entry.set('itertools') + Equal(self.dialog.entry_ok(), None) + Equal(showerror.title, 'Import Error') + self.assertIn('source-based', showerror.message) + + def test_good_module_name(self): + dialog = self.dialog + Equal = self.assertEqual + dialog.entry.set('idlelib') + self.assertTrue(dialog.entry_ok().endswith('__init__.py')) + Equal(showerror.title, None) + + class QueryGuiTest(unittest.TestCase): @classmethod def setUpClass(cls): requires('gui') - cls.root = Tk() - cls.dialog = Query(cls.root, 'TEST', 'test', _utest=True) + cls.root = root = Tk() + cls.dialog = Query(root, 'TEST', 'test', _utest=True) cls.dialog.destroy = mock.Mock() @classmethod @@ -160,5 +200,43 @@ class QueryGuiTest(unittest.TestCase): self.assertTrue(dialog.destroy.called) +class SectionnameGuiTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + + def test_click_section_name(self): + root = Tk() + dialog = query.SectionName(root, 'T', 't', {'abc'}, _utest=True) + Equal = self.assertEqual + Equal(dialog.used_names, {'abc'}) + dialog.entry.insert(0, 'okay') + dialog.button_ok.invoke() + Equal(dialog.result, 'okay') + del dialog + root.destroy() + del root + + +class ModulenameGuiTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + + def test_click_module_name(self): + root = Tk() + dialog = query.ModuleName(root, 'T', 't', 'idlelib', _utest=True) + Equal = self.assertEqual + Equal(dialog.text0, 'idlelib') + Equal(dialog.entry.get(), 'idlelib') + dialog.button_ok.invoke() + self.assertTrue(dialog.result.endswith('__init__.py')) + del dialog + root.destroy() + del root + + if __name__ == '__main__': unittest.main(verbosity=2, exit=False) diff --git a/Lib/idlelib/query.py b/Lib/idlelib/query.py index e3937a1..fd9716f 100644 --- a/Lib/idlelib/query.py +++ b/Lib/idlelib/query.py @@ -15,7 +15,8 @@ Configdialog uses it for new highlight theme and keybinding set names. # of configSectionNameDialog.py (temporarily config_sec.py) into # generic and specific parts. -from tkinter import FALSE, TRUE, Toplevel +import importlib +from tkinter import Toplevel, StringVar from tkinter.messagebox import showerror from tkinter.ttk import Frame, Button, Entry, Label @@ -24,20 +25,22 @@ class Query(Toplevel): For this base class, accept any non-blank string. """ - def __init__(self, parent, title, message, - *, _htest=False, _utest=False): # Call from override. + def __init__(self, parent, title, message, text0='', + *, _htest=False, _utest=False): """Create popup, do not return until tk widget destroyed. - Additional subclass init must be done before calling this. + Additional subclass init must be done before calling this + unless _utest=True is passed to suppress wait_window(). title - string, title of popup dialog message - string, informational message to display + text0 - initial value for entry _htest - bool, change box location when running htest _utest - bool, leave window hidden and not modal """ Toplevel.__init__(self, parent) self.configure(borderwidth=5) - self.resizable(height=FALSE, width=FALSE) + self.resizable(height=False, width=False) self.title(title) self.transient(parent) self.grab_set() @@ -45,6 +48,7 @@ class Query(Toplevel): self.protocol("WM_DELETE_WINDOW", self.cancel) self.parent = parent self.message = message + self.text0 = text0 self.create_widgets() self.update_idletasks() #needs to be done here so that the winfo_reqwidth is valid @@ -62,31 +66,34 @@ class Query(Toplevel): self.wait_window() def create_widgets(self): # Call from override, if any. + # Bind widgets needed for entry_ok or unittest to self. frame = Frame(self, borderwidth=2, relief='sunken', ) label = Label(frame, anchor='w', justify='left', text=self.message) - self.entry = Entry(frame, width=30) # Bind name for entry_ok. + self.entryvar = StringVar(self, self.text0) + self.entry = Entry(frame, width=30, textvariable=self.entryvar) self.entry.focus_set() - buttons = Frame(self) # Bind buttons for invoke in unittest. + buttons = Frame(self) self.button_ok = Button(buttons, text='Ok', width=8, command=self.ok) self.button_cancel = Button(buttons, text='Cancel', width=8, command=self.cancel) - frame.pack(side='top', expand=TRUE, fill='both') + frame.pack(side='top', expand=True, fill='both') label.pack(padx=5, pady=5) self.entry.pack(padx=5, pady=5) buttons.pack(side='bottom') self.button_ok.pack(side='left', padx=5) self.button_cancel.pack(side='right', padx=5) - def entry_ok(self): # Usually replace. - "Check that entry not blank." + def entry_ok(self): # Example: usually replace. + "Return non-blank entry or None." entry = self.entry.get().strip() if not entry: showerror(title='Entry Error', message='Blank line.', parent=self) + return return entry def ok(self, event=None): # Do not replace. @@ -95,7 +102,7 @@ class Query(Toplevel): Otherwise leave dialog open for user to correct entry or cancel. ''' entry = self.entry_ok() - if entry: + if entry is not None: self.result = entry self.destroy() else: @@ -114,32 +121,72 @@ class SectionName(Query): def __init__(self, parent, title, message, used_names, *, _htest=False, _utest=False): "used_names - collection of strings already in use" - self.used_names = used_names Query.__init__(self, parent, title, message, _htest=_htest, _utest=_utest) - # This call does ot return until tk widget is destroyed. def entry_ok(self): - '''Stripping entered name, check that it is a sensible - ConfigParser file section name. Return it if it is, '' if not. - ''' + "Return sensible ConfigParser section name or None." name = self.entry.get().strip() if not name: showerror(title='Name Error', message='No name specified.', parent=self) + return elif len(name)>30: showerror(title='Name Error', message='Name too long. It should be no more than '+ '30 characters.', parent=self) - name = '' + return elif name in self.used_names: showerror(title='Name Error', message='This name is already in use.', parent=self) - name = '' + return return name +class ModuleName(Query): + "Get a module name for Open Module menu entry." + # Used in open_module (editor.EditorWindow until move to iobinding). + + def __init__(self, parent, title, message, text0='', + *, _htest=False, _utest=False): + """text0 - name selected in text before Open Module invoked" + """ + Query.__init__(self, parent, title, message, text0=text0, + _htest=_htest, _utest=_utest) + + def entry_ok(self): + "Return entered module name as file path or None." + # Moved here from Editor_Window.load_module 2016 July. + name = self.entry.get().strip() + if not name: + showerror(title='Name Error', + message='No name specified.', parent=self) + return + # XXX Ought to insert current file's directory in front of path + try: + spec = importlib.util.find_spec(name) + except (ValueError, ImportError) as msg: + showerror("Import Error", str(msg), parent=self) + return + if spec is None: + showerror("Import Error", "module not found", + parent=self) + return + if not isinstance(spec.loader, importlib.abc.SourceLoader): + showerror("Import Error", "not a source-based module", + parent=self) + return + try: + file_path = spec.loader.get_filename(name) + except AttributeError: + showerror("Import Error", + "loader does not support get_filename", + parent=self) + return + return file_path + + if __name__ == '__main__': import unittest unittest.main('idlelib.idle_test.test_query', verbosity=2, exit=False) |