##// END OF EJS Templates
Merge pull request #11227 from yen223/11226-3-7-autocomplete-bugfix...
Matthias Bussonnier -
r24422:134f9705 merge
parent child Browse files
Show More
@@ -1,354 +1,354 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Implementations for various useful completers.
2 """Implementations for various useful completers.
3
3
4 These are all loaded by default by IPython.
4 These are all loaded by default by IPython.
5 """
5 """
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2010-2011 The IPython Development Team.
7 # Copyright (C) 2010-2011 The IPython Development Team.
8 #
8 #
9 # Distributed under the terms of the BSD License.
9 # Distributed under the terms of the BSD License.
10 #
10 #
11 # The full license is in the file COPYING.txt, distributed with this software.
11 # The full license is in the file COPYING.txt, distributed with this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Stdlib imports
18 # Stdlib imports
19 import glob
19 import glob
20 import inspect
20 import inspect
21 import os
21 import os
22 import re
22 import re
23 import sys
23 import sys
24 from importlib import import_module
24 from importlib import import_module
25 from importlib.machinery import all_suffixes
25 from importlib.machinery import all_suffixes
26
26
27
27
28 # Third-party imports
28 # Third-party imports
29 from time import time
29 from time import time
30 from zipimport import zipimporter
30 from zipimport import zipimporter
31
31
32 # Our own imports
32 # Our own imports
33 from IPython.core.completer import expand_user, compress_user
33 from IPython.core.completer import expand_user, compress_user
34 from IPython.core.error import TryNext
34 from IPython.core.error import TryNext
35 from IPython.utils._process_common import arg_split
35 from IPython.utils._process_common import arg_split
36
36
37 # FIXME: this should be pulled in with the right call via the component system
37 # FIXME: this should be pulled in with the right call via the component system
38 from IPython import get_ipython
38 from IPython import get_ipython
39
39
40 from typing import List
40 from typing import List
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # Globals and constants
43 # Globals and constants
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 _suffixes = all_suffixes()
45 _suffixes = all_suffixes()
46
46
47 # Time in seconds after which the rootmodules will be stored permanently in the
47 # Time in seconds after which the rootmodules will be stored permanently in the
48 # ipython ip.db database (kept in the user's .ipython dir).
48 # ipython ip.db database (kept in the user's .ipython dir).
49 TIMEOUT_STORAGE = 2
49 TIMEOUT_STORAGE = 2
50
50
51 # Time in seconds after which we give up
51 # Time in seconds after which we give up
52 TIMEOUT_GIVEUP = 20
52 TIMEOUT_GIVEUP = 20
53
53
54 # Regular expression for the python import statement
54 # Regular expression for the python import statement
55 import_re = re.compile(r'(?P<name>[a-zA-Z_][a-zA-Z0-9_]*?)'
55 import_re = re.compile(r'(?P<name>[a-zA-Z_][a-zA-Z0-9_]*?)'
56 r'(?P<package>[/\\]__init__)?'
56 r'(?P<package>[/\\]__init__)?'
57 r'(?P<suffix>%s)$' %
57 r'(?P<suffix>%s)$' %
58 r'|'.join(re.escape(s) for s in _suffixes))
58 r'|'.join(re.escape(s) for s in _suffixes))
59
59
60 # RE for the ipython %run command (python + ipython scripts)
60 # RE for the ipython %run command (python + ipython scripts)
61 magic_run_re = re.compile(r'.*(\.ipy|\.ipynb|\.py[w]?)$')
61 magic_run_re = re.compile(r'.*(\.ipy|\.ipynb|\.py[w]?)$')
62
62
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64 # Local utilities
64 # Local utilities
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66
66
67 def module_list(path):
67 def module_list(path):
68 """
68 """
69 Return the list containing the names of the modules available in the given
69 Return the list containing the names of the modules available in the given
70 folder.
70 folder.
71 """
71 """
72 # sys.path has the cwd as an empty string, but isdir/listdir need it as '.'
72 # sys.path has the cwd as an empty string, but isdir/listdir need it as '.'
73 if path == '':
73 if path == '':
74 path = '.'
74 path = '.'
75
75
76 # A few local constants to be used in loops below
76 # A few local constants to be used in loops below
77 pjoin = os.path.join
77 pjoin = os.path.join
78
78
79 if os.path.isdir(path):
79 if os.path.isdir(path):
80 # Build a list of all files in the directory and all files
80 # Build a list of all files in the directory and all files
81 # in its subdirectories. For performance reasons, do not
81 # in its subdirectories. For performance reasons, do not
82 # recurse more than one level into subdirectories.
82 # recurse more than one level into subdirectories.
83 files = []
83 files = []
84 for root, dirs, nondirs in os.walk(path, followlinks=True):
84 for root, dirs, nondirs in os.walk(path, followlinks=True):
85 subdir = root[len(path)+1:]
85 subdir = root[len(path)+1:]
86 if subdir:
86 if subdir:
87 files.extend(pjoin(subdir, f) for f in nondirs)
87 files.extend(pjoin(subdir, f) for f in nondirs)
88 dirs[:] = [] # Do not recurse into additional subdirectories.
88 dirs[:] = [] # Do not recurse into additional subdirectories.
89 else:
89 else:
90 files.extend(nondirs)
90 files.extend(nondirs)
91
91
92 else:
92 else:
93 try:
93 try:
94 files = list(zipimporter(path)._files.keys())
94 files = list(zipimporter(path)._files.keys())
95 except:
95 except:
96 files = []
96 files = []
97
97
98 # Build a list of modules which match the import_re regex.
98 # Build a list of modules which match the import_re regex.
99 modules = []
99 modules = []
100 for f in files:
100 for f in files:
101 m = import_re.match(f)
101 m = import_re.match(f)
102 if m:
102 if m:
103 modules.append(m.group('name'))
103 modules.append(m.group('name'))
104 return list(set(modules))
104 return list(set(modules))
105
105
106
106
107 def get_root_modules():
107 def get_root_modules():
108 """
108 """
109 Returns a list containing the names of all the modules available in the
109 Returns a list containing the names of all the modules available in the
110 folders of the pythonpath.
110 folders of the pythonpath.
111
111
112 ip.db['rootmodules_cache'] maps sys.path entries to list of modules.
112 ip.db['rootmodules_cache'] maps sys.path entries to list of modules.
113 """
113 """
114 ip = get_ipython()
114 ip = get_ipython()
115 if ip is None:
115 if ip is None:
116 # No global shell instance to store cached list of modules.
116 # No global shell instance to store cached list of modules.
117 # Don't try to scan for modules every time.
117 # Don't try to scan for modules every time.
118 return list(sys.builtin_module_names)
118 return list(sys.builtin_module_names)
119
119
120 rootmodules_cache = ip.db.get('rootmodules_cache', {})
120 rootmodules_cache = ip.db.get('rootmodules_cache', {})
121 rootmodules = list(sys.builtin_module_names)
121 rootmodules = list(sys.builtin_module_names)
122 start_time = time()
122 start_time = time()
123 store = False
123 store = False
124 for path in sys.path:
124 for path in sys.path:
125 try:
125 try:
126 modules = rootmodules_cache[path]
126 modules = rootmodules_cache[path]
127 except KeyError:
127 except KeyError:
128 modules = module_list(path)
128 modules = module_list(path)
129 try:
129 try:
130 modules.remove('__init__')
130 modules.remove('__init__')
131 except ValueError:
131 except ValueError:
132 pass
132 pass
133 if path not in ('', '.'): # cwd modules should not be cached
133 if path not in ('', '.'): # cwd modules should not be cached
134 rootmodules_cache[path] = modules
134 rootmodules_cache[path] = modules
135 if time() - start_time > TIMEOUT_STORAGE and not store:
135 if time() - start_time > TIMEOUT_STORAGE and not store:
136 store = True
136 store = True
137 print("\nCaching the list of root modules, please wait!")
137 print("\nCaching the list of root modules, please wait!")
138 print("(This will only be done once - type '%rehashx' to "
138 print("(This will only be done once - type '%rehashx' to "
139 "reset cache!)\n")
139 "reset cache!)\n")
140 sys.stdout.flush()
140 sys.stdout.flush()
141 if time() - start_time > TIMEOUT_GIVEUP:
141 if time() - start_time > TIMEOUT_GIVEUP:
142 print("This is taking too long, we give up.\n")
142 print("This is taking too long, we give up.\n")
143 return []
143 return []
144 rootmodules.extend(modules)
144 rootmodules.extend(modules)
145 if store:
145 if store:
146 ip.db['rootmodules_cache'] = rootmodules_cache
146 ip.db['rootmodules_cache'] = rootmodules_cache
147 rootmodules = list(set(rootmodules))
147 rootmodules = list(set(rootmodules))
148 return rootmodules
148 return rootmodules
149
149
150
150
151 def is_importable(module, attr, only_modules):
151 def is_importable(module, attr, only_modules):
152 if only_modules:
152 if only_modules:
153 return inspect.ismodule(getattr(module, attr))
153 return inspect.ismodule(getattr(module, attr))
154 else:
154 else:
155 return not(attr[:2] == '__' and attr[-2:] == '__')
155 return not(attr[:2] == '__' and attr[-2:] == '__')
156
156
157
157
158 def try_import(mod: str, only_modules=False) -> List[str]:
158 def try_import(mod: str, only_modules=False) -> List[str]:
159 """
159 """
160 Try to import given module and return list of potential completions.
160 Try to import given module and return list of potential completions.
161 """
161 """
162 mod = mod.rstrip('.')
162 mod = mod.rstrip('.')
163 try:
163 try:
164 m = import_module(mod)
164 m = import_module(mod)
165 except:
165 except:
166 return []
166 return []
167
167
168 m_is_init = hasattr(m, '__file__') and '__init__' in m.__file__
168 m_is_init = '__init__' in (getattr(m, '__file__', '') or '')
169
169
170 completions = []
170 completions = []
171 if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init:
171 if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init:
172 completions.extend( [attr for attr in dir(m) if
172 completions.extend( [attr for attr in dir(m) if
173 is_importable(m, attr, only_modules)])
173 is_importable(m, attr, only_modules)])
174
174
175 completions.extend(getattr(m, '__all__', []))
175 completions.extend(getattr(m, '__all__', []))
176 if m_is_init:
176 if m_is_init:
177 completions.extend(module_list(os.path.dirname(m.__file__)))
177 completions.extend(module_list(os.path.dirname(m.__file__)))
178 completions_set = {c for c in completions if isinstance(c, str)}
178 completions_set = {c for c in completions if isinstance(c, str)}
179 completions_set.discard('__init__')
179 completions_set.discard('__init__')
180 return list(completions_set)
180 return list(completions_set)
181
181
182
182
183 #-----------------------------------------------------------------------------
183 #-----------------------------------------------------------------------------
184 # Completion-related functions.
184 # Completion-related functions.
185 #-----------------------------------------------------------------------------
185 #-----------------------------------------------------------------------------
186
186
187 def quick_completer(cmd, completions):
187 def quick_completer(cmd, completions):
188 """ Easily create a trivial completer for a command.
188 """ Easily create a trivial completer for a command.
189
189
190 Takes either a list of completions, or all completions in string (that will
190 Takes either a list of completions, or all completions in string (that will
191 be split on whitespace).
191 be split on whitespace).
192
192
193 Example::
193 Example::
194
194
195 [d:\ipython]|1> import ipy_completers
195 [d:\ipython]|1> import ipy_completers
196 [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz'])
196 [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz'])
197 [d:\ipython]|3> foo b<TAB>
197 [d:\ipython]|3> foo b<TAB>
198 bar baz
198 bar baz
199 [d:\ipython]|3> foo ba
199 [d:\ipython]|3> foo ba
200 """
200 """
201
201
202 if isinstance(completions, str):
202 if isinstance(completions, str):
203 completions = completions.split()
203 completions = completions.split()
204
204
205 def do_complete(self, event):
205 def do_complete(self, event):
206 return completions
206 return completions
207
207
208 get_ipython().set_hook('complete_command',do_complete, str_key = cmd)
208 get_ipython().set_hook('complete_command',do_complete, str_key = cmd)
209
209
210 def module_completion(line):
210 def module_completion(line):
211 """
211 """
212 Returns a list containing the completion possibilities for an import line.
212 Returns a list containing the completion possibilities for an import line.
213
213
214 The line looks like this :
214 The line looks like this :
215 'import xml.d'
215 'import xml.d'
216 'from xml.dom import'
216 'from xml.dom import'
217 """
217 """
218
218
219 words = line.split(' ')
219 words = line.split(' ')
220 nwords = len(words)
220 nwords = len(words)
221
221
222 # from whatever <tab> -> 'import '
222 # from whatever <tab> -> 'import '
223 if nwords == 3 and words[0] == 'from':
223 if nwords == 3 and words[0] == 'from':
224 return ['import ']
224 return ['import ']
225
225
226 # 'from xy<tab>' or 'import xy<tab>'
226 # 'from xy<tab>' or 'import xy<tab>'
227 if nwords < 3 and (words[0] in {'%aimport', 'import', 'from'}) :
227 if nwords < 3 and (words[0] in {'%aimport', 'import', 'from'}) :
228 if nwords == 1:
228 if nwords == 1:
229 return get_root_modules()
229 return get_root_modules()
230 mod = words[1].split('.')
230 mod = words[1].split('.')
231 if len(mod) < 2:
231 if len(mod) < 2:
232 return get_root_modules()
232 return get_root_modules()
233 completion_list = try_import('.'.join(mod[:-1]), True)
233 completion_list = try_import('.'.join(mod[:-1]), True)
234 return ['.'.join(mod[:-1] + [el]) for el in completion_list]
234 return ['.'.join(mod[:-1] + [el]) for el in completion_list]
235
235
236 # 'from xyz import abc<tab>'
236 # 'from xyz import abc<tab>'
237 if nwords >= 3 and words[0] == 'from':
237 if nwords >= 3 and words[0] == 'from':
238 mod = words[1]
238 mod = words[1]
239 return try_import(mod)
239 return try_import(mod)
240
240
241 #-----------------------------------------------------------------------------
241 #-----------------------------------------------------------------------------
242 # Completers
242 # Completers
243 #-----------------------------------------------------------------------------
243 #-----------------------------------------------------------------------------
244 # These all have the func(self, event) signature to be used as custom
244 # These all have the func(self, event) signature to be used as custom
245 # completers
245 # completers
246
246
247 def module_completer(self,event):
247 def module_completer(self,event):
248 """Give completions after user has typed 'import ...' or 'from ...'"""
248 """Give completions after user has typed 'import ...' or 'from ...'"""
249
249
250 # This works in all versions of python. While 2.5 has
250 # This works in all versions of python. While 2.5 has
251 # pkgutil.walk_packages(), that particular routine is fairly dangerous,
251 # pkgutil.walk_packages(), that particular routine is fairly dangerous,
252 # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full
252 # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full
253 # of possibly problematic side effects.
253 # of possibly problematic side effects.
254 # This search the folders in the sys.path for available modules.
254 # This search the folders in the sys.path for available modules.
255
255
256 return module_completion(event.line)
256 return module_completion(event.line)
257
257
258 # FIXME: there's a lot of logic common to the run, cd and builtin file
258 # FIXME: there's a lot of logic common to the run, cd and builtin file
259 # completers, that is currently reimplemented in each.
259 # completers, that is currently reimplemented in each.
260
260
261 def magic_run_completer(self, event):
261 def magic_run_completer(self, event):
262 """Complete files that end in .py or .ipy or .ipynb for the %run command.
262 """Complete files that end in .py or .ipy or .ipynb for the %run command.
263 """
263 """
264 comps = arg_split(event.line, strict=False)
264 comps = arg_split(event.line, strict=False)
265 # relpath should be the current token that we need to complete.
265 # relpath should be the current token that we need to complete.
266 if (len(comps) > 1) and (not event.line.endswith(' ')):
266 if (len(comps) > 1) and (not event.line.endswith(' ')):
267 relpath = comps[-1].strip("'\"")
267 relpath = comps[-1].strip("'\"")
268 else:
268 else:
269 relpath = ''
269 relpath = ''
270
270
271 #print("\nev=", event) # dbg
271 #print("\nev=", event) # dbg
272 #print("rp=", relpath) # dbg
272 #print("rp=", relpath) # dbg
273 #print('comps=', comps) # dbg
273 #print('comps=', comps) # dbg
274
274
275 lglob = glob.glob
275 lglob = glob.glob
276 isdir = os.path.isdir
276 isdir = os.path.isdir
277 relpath, tilde_expand, tilde_val = expand_user(relpath)
277 relpath, tilde_expand, tilde_val = expand_user(relpath)
278
278
279 # Find if the user has already typed the first filename, after which we
279 # Find if the user has already typed the first filename, after which we
280 # should complete on all files, since after the first one other files may
280 # should complete on all files, since after the first one other files may
281 # be arguments to the input script.
281 # be arguments to the input script.
282
282
283 if any(magic_run_re.match(c) for c in comps):
283 if any(magic_run_re.match(c) for c in comps):
284 matches = [f.replace('\\','/') + ('/' if isdir(f) else '')
284 matches = [f.replace('\\','/') + ('/' if isdir(f) else '')
285 for f in lglob(relpath+'*')]
285 for f in lglob(relpath+'*')]
286 else:
286 else:
287 dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)]
287 dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)]
288 pys = [f.replace('\\','/')
288 pys = [f.replace('\\','/')
289 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
289 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
290 lglob(relpath+'*.ipynb') + lglob(relpath + '*.pyw')]
290 lglob(relpath+'*.ipynb') + lglob(relpath + '*.pyw')]
291
291
292 matches = dirs + pys
292 matches = dirs + pys
293
293
294 #print('run comp:', dirs+pys) # dbg
294 #print('run comp:', dirs+pys) # dbg
295 return [compress_user(p, tilde_expand, tilde_val) for p in matches]
295 return [compress_user(p, tilde_expand, tilde_val) for p in matches]
296
296
297
297
298 def cd_completer(self, event):
298 def cd_completer(self, event):
299 """Completer function for cd, which only returns directories."""
299 """Completer function for cd, which only returns directories."""
300 ip = get_ipython()
300 ip = get_ipython()
301 relpath = event.symbol
301 relpath = event.symbol
302
302
303 #print(event) # dbg
303 #print(event) # dbg
304 if event.line.endswith('-b') or ' -b ' in event.line:
304 if event.line.endswith('-b') or ' -b ' in event.line:
305 # return only bookmark completions
305 # return only bookmark completions
306 bkms = self.db.get('bookmarks', None)
306 bkms = self.db.get('bookmarks', None)
307 if bkms:
307 if bkms:
308 return bkms.keys()
308 return bkms.keys()
309 else:
309 else:
310 return []
310 return []
311
311
312 if event.symbol == '-':
312 if event.symbol == '-':
313 width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
313 width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
314 # jump in directory history by number
314 # jump in directory history by number
315 fmt = '-%0' + width_dh +'d [%s]'
315 fmt = '-%0' + width_dh +'d [%s]'
316 ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
316 ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
317 if len(ents) > 1:
317 if len(ents) > 1:
318 return ents
318 return ents
319 return []
319 return []
320
320
321 if event.symbol.startswith('--'):
321 if event.symbol.startswith('--'):
322 return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']]
322 return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']]
323
323
324 # Expand ~ in path and normalize directory separators.
324 # Expand ~ in path and normalize directory separators.
325 relpath, tilde_expand, tilde_val = expand_user(relpath)
325 relpath, tilde_expand, tilde_val = expand_user(relpath)
326 relpath = relpath.replace('\\','/')
326 relpath = relpath.replace('\\','/')
327
327
328 found = []
328 found = []
329 for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
329 for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
330 if os.path.isdir(f)]:
330 if os.path.isdir(f)]:
331 if ' ' in d:
331 if ' ' in d:
332 # we don't want to deal with any of that, complex code
332 # we don't want to deal with any of that, complex code
333 # for this is elsewhere
333 # for this is elsewhere
334 raise TryNext
334 raise TryNext
335
335
336 found.append(d)
336 found.append(d)
337
337
338 if not found:
338 if not found:
339 if os.path.isdir(relpath):
339 if os.path.isdir(relpath):
340 return [compress_user(relpath, tilde_expand, tilde_val)]
340 return [compress_user(relpath, tilde_expand, tilde_val)]
341
341
342 # if no completions so far, try bookmarks
342 # if no completions so far, try bookmarks
343 bks = self.db.get('bookmarks',{})
343 bks = self.db.get('bookmarks',{})
344 bkmatches = [s for s in bks if s.startswith(event.symbol)]
344 bkmatches = [s for s in bks if s.startswith(event.symbol)]
345 if bkmatches:
345 if bkmatches:
346 return bkmatches
346 return bkmatches
347
347
348 raise TryNext
348 raise TryNext
349
349
350 return [compress_user(p, tilde_expand, tilde_val) for p in found]
350 return [compress_user(p, tilde_expand, tilde_val) for p in found]
351
351
352 def reset_completer(self, event):
352 def reset_completer(self, event):
353 "A completer for %reset magic"
353 "A completer for %reset magic"
354 return '-f -s in out array dhist'.split()
354 return '-f -s in out array dhist'.split()
@@ -1,161 +1,178 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for completerlib.
2 """Tests for completerlib.
3
3
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Imports
7 # Imports
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 import os
10 import os
11 import shutil
11 import shutil
12 import sys
12 import sys
13 import tempfile
13 import tempfile
14 import unittest
14 import unittest
15 from os.path import join
15 from os.path import join
16
16
17 import nose.tools as nt
17 import nose.tools as nt
18
18
19 from IPython.core.completerlib import magic_run_completer, module_completion
19 from IPython.core.completerlib import magic_run_completer, module_completion, try_import
20 from IPython.utils.tempdir import TemporaryDirectory
20 from IPython.utils.tempdir import TemporaryDirectory
21 from IPython.testing.decorators import onlyif_unicode_paths
21 from IPython.testing.decorators import onlyif_unicode_paths
22
22
23
23
24 class MockEvent(object):
24 class MockEvent(object):
25 def __init__(self, line):
25 def __init__(self, line):
26 self.line = line
26 self.line = line
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Test functions begin
29 # Test functions begin
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 class Test_magic_run_completer(unittest.TestCase):
31 class Test_magic_run_completer(unittest.TestCase):
32 files = [u"aao.py", u"a.py", u"b.py", u"aao.txt"]
32 files = [u"aao.py", u"a.py", u"b.py", u"aao.txt"]
33 dirs = [u"adir/", "bdir/"]
33 dirs = [u"adir/", "bdir/"]
34
34
35 def setUp(self):
35 def setUp(self):
36 self.BASETESTDIR = tempfile.mkdtemp()
36 self.BASETESTDIR = tempfile.mkdtemp()
37 for fil in self.files:
37 for fil in self.files:
38 with open(join(self.BASETESTDIR, fil), "w") as sfile:
38 with open(join(self.BASETESTDIR, fil), "w") as sfile:
39 sfile.write("pass\n")
39 sfile.write("pass\n")
40 for d in self.dirs:
40 for d in self.dirs:
41 os.mkdir(join(self.BASETESTDIR, d))
41 os.mkdir(join(self.BASETESTDIR, d))
42
42
43 self.oldpath = os.getcwd()
43 self.oldpath = os.getcwd()
44 os.chdir(self.BASETESTDIR)
44 os.chdir(self.BASETESTDIR)
45
45
46 def tearDown(self):
46 def tearDown(self):
47 os.chdir(self.oldpath)
47 os.chdir(self.oldpath)
48 shutil.rmtree(self.BASETESTDIR)
48 shutil.rmtree(self.BASETESTDIR)
49
49
50 def test_1(self):
50 def test_1(self):
51 """Test magic_run_completer, should match two alterntives
51 """Test magic_run_completer, should match two alterntives
52 """
52 """
53 event = MockEvent(u"%run a")
53 event = MockEvent(u"%run a")
54 mockself = None
54 mockself = None
55 match = set(magic_run_completer(mockself, event))
55 match = set(magic_run_completer(mockself, event))
56 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
56 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
57
57
58 def test_2(self):
58 def test_2(self):
59 """Test magic_run_completer, should match one alterntive
59 """Test magic_run_completer, should match one alterntive
60 """
60 """
61 event = MockEvent(u"%run aa")
61 event = MockEvent(u"%run aa")
62 mockself = None
62 mockself = None
63 match = set(magic_run_completer(mockself, event))
63 match = set(magic_run_completer(mockself, event))
64 self.assertEqual(match, {u"aao.py"})
64 self.assertEqual(match, {u"aao.py"})
65
65
66 def test_3(self):
66 def test_3(self):
67 """Test magic_run_completer with unterminated " """
67 """Test magic_run_completer with unterminated " """
68 event = MockEvent(u'%run "a')
68 event = MockEvent(u'%run "a')
69 mockself = None
69 mockself = None
70 match = set(magic_run_completer(mockself, event))
70 match = set(magic_run_completer(mockself, event))
71 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
71 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
72
72
73 def test_completion_more_args(self):
73 def test_completion_more_args(self):
74 event = MockEvent(u'%run a.py ')
74 event = MockEvent(u'%run a.py ')
75 match = set(magic_run_completer(None, event))
75 match = set(magic_run_completer(None, event))
76 self.assertEqual(match, set(self.files + self.dirs))
76 self.assertEqual(match, set(self.files + self.dirs))
77
77
78 def test_completion_in_dir(self):
78 def test_completion_in_dir(self):
79 # Github issue #3459
79 # Github issue #3459
80 event = MockEvent(u'%run a.py {}'.format(join(self.BASETESTDIR, 'a')))
80 event = MockEvent(u'%run a.py {}'.format(join(self.BASETESTDIR, 'a')))
81 print(repr(event.line))
81 print(repr(event.line))
82 match = set(magic_run_completer(None, event))
82 match = set(magic_run_completer(None, event))
83 # We specifically use replace here rather than normpath, because
83 # We specifically use replace here rather than normpath, because
84 # at one point there were duplicates 'adir' and 'adir/', and normpath
84 # at one point there were duplicates 'adir' and 'adir/', and normpath
85 # would hide the failure for that.
85 # would hide the failure for that.
86 self.assertEqual(match, {join(self.BASETESTDIR, f).replace('\\','/')
86 self.assertEqual(match, {join(self.BASETESTDIR, f).replace('\\','/')
87 for f in (u'a.py', u'aao.py', u'aao.txt', u'adir/')})
87 for f in (u'a.py', u'aao.py', u'aao.txt', u'adir/')})
88
88
89 class Test_magic_run_completer_nonascii(unittest.TestCase):
89 class Test_magic_run_completer_nonascii(unittest.TestCase):
90 @onlyif_unicode_paths
90 @onlyif_unicode_paths
91 def setUp(self):
91 def setUp(self):
92 self.BASETESTDIR = tempfile.mkdtemp()
92 self.BASETESTDIR = tempfile.mkdtemp()
93 for fil in [u"aaΓΈ.py", u"a.py", u"b.py"]:
93 for fil in [u"aaΓΈ.py", u"a.py", u"b.py"]:
94 with open(join(self.BASETESTDIR, fil), "w") as sfile:
94 with open(join(self.BASETESTDIR, fil), "w") as sfile:
95 sfile.write("pass\n")
95 sfile.write("pass\n")
96 self.oldpath = os.getcwd()
96 self.oldpath = os.getcwd()
97 os.chdir(self.BASETESTDIR)
97 os.chdir(self.BASETESTDIR)
98
98
99 def tearDown(self):
99 def tearDown(self):
100 os.chdir(self.oldpath)
100 os.chdir(self.oldpath)
101 shutil.rmtree(self.BASETESTDIR)
101 shutil.rmtree(self.BASETESTDIR)
102
102
103 @onlyif_unicode_paths
103 @onlyif_unicode_paths
104 def test_1(self):
104 def test_1(self):
105 """Test magic_run_completer, should match two alterntives
105 """Test magic_run_completer, should match two alterntives
106 """
106 """
107 event = MockEvent(u"%run a")
107 event = MockEvent(u"%run a")
108 mockself = None
108 mockself = None
109 match = set(magic_run_completer(mockself, event))
109 match = set(magic_run_completer(mockself, event))
110 self.assertEqual(match, {u"a.py", u"aaΓΈ.py"})
110 self.assertEqual(match, {u"a.py", u"aaΓΈ.py"})
111
111
112 @onlyif_unicode_paths
112 @onlyif_unicode_paths
113 def test_2(self):
113 def test_2(self):
114 """Test magic_run_completer, should match one alterntive
114 """Test magic_run_completer, should match one alterntive
115 """
115 """
116 event = MockEvent(u"%run aa")
116 event = MockEvent(u"%run aa")
117 mockself = None
117 mockself = None
118 match = set(magic_run_completer(mockself, event))
118 match = set(magic_run_completer(mockself, event))
119 self.assertEqual(match, {u"aaΓΈ.py"})
119 self.assertEqual(match, {u"aaΓΈ.py"})
120
120
121 @onlyif_unicode_paths
121 @onlyif_unicode_paths
122 def test_3(self):
122 def test_3(self):
123 """Test magic_run_completer with unterminated " """
123 """Test magic_run_completer with unterminated " """
124 event = MockEvent(u'%run "a')
124 event = MockEvent(u'%run "a')
125 mockself = None
125 mockself = None
126 match = set(magic_run_completer(mockself, event))
126 match = set(magic_run_completer(mockself, event))
127 self.assertEqual(match, {u"a.py", u"aaΓΈ.py"})
127 self.assertEqual(match, {u"a.py", u"aaΓΈ.py"})
128
128
129 # module_completer:
129 # module_completer:
130
130
131 def test_import_invalid_module():
131 def test_import_invalid_module():
132 """Testing of issue https://github.com/ipython/ipython/issues/1107"""
132 """Testing of issue https://github.com/ipython/ipython/issues/1107"""
133 invalid_module_names = {'foo-bar', 'foo:bar', '10foo'}
133 invalid_module_names = {'foo-bar', 'foo:bar', '10foo'}
134 valid_module_names = {'foobar'}
134 valid_module_names = {'foobar'}
135 with TemporaryDirectory() as tmpdir:
135 with TemporaryDirectory() as tmpdir:
136 sys.path.insert( 0, tmpdir )
136 sys.path.insert( 0, tmpdir )
137 for name in invalid_module_names | valid_module_names:
137 for name in invalid_module_names | valid_module_names:
138 filename = os.path.join(tmpdir, name + '.py')
138 filename = os.path.join(tmpdir, name + '.py')
139 open(filename, 'w').close()
139 open(filename, 'w').close()
140
140
141 s = set( module_completion('import foo') )
141 s = set( module_completion('import foo') )
142 intersection = s.intersection(invalid_module_names)
142 intersection = s.intersection(invalid_module_names)
143 nt.assert_equal(intersection, set())
143 nt.assert_equal(intersection, set())
144
144
145 assert valid_module_names.issubset(s), valid_module_names.intersection(s)
145 assert valid_module_names.issubset(s), valid_module_names.intersection(s)
146
146
147
147
148 def test_bad_module_all():
148 def test_bad_module_all():
149 """Test module with invalid __all__
149 """Test module with invalid __all__
150
150
151 https://github.com/ipython/ipython/issues/9678
151 https://github.com/ipython/ipython/issues/9678
152 """
152 """
153 testsdir = os.path.dirname(__file__)
153 testsdir = os.path.dirname(__file__)
154 sys.path.insert(0, testsdir)
154 sys.path.insert(0, testsdir)
155 try:
155 try:
156 results = module_completion('from bad_all import ')
156 results = module_completion('from bad_all import ')
157 nt.assert_in('puppies', results)
157 nt.assert_in('puppies', results)
158 for r in results:
158 for r in results:
159 nt.assert_is_instance(r, str)
159 nt.assert_is_instance(r, str)
160 finally:
160 finally:
161 sys.path.remove(testsdir)
161 sys.path.remove(testsdir)
162
163
164 def test_module_without_init():
165 """
166 Test module without __init__.py.
167
168 https://github.com/ipython/ipython/issues/11226
169 """
170 fake_module_name = "foo"
171 with TemporaryDirectory() as tmpdir:
172 sys.path.insert(0, tmpdir)
173 try:
174 os.makedirs(os.path.join(tmpdir, fake_module_name))
175 s = try_import(mod=fake_module_name)
176 assert s == []
177 finally:
178 sys.path.remove(tmpdir)
General Comments 0
You need to be logged in to leave comments. Login now