summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/idlelib/README.txt6
-rw-r--r--Lib/idlelib/editor.py58
-rw-r--r--Lib/idlelib/idle_test/htest.py5
-rw-r--r--Lib/idlelib/idle_test/test_query.py110
-rw-r--r--Lib/idlelib/query.py83
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)