##// END OF EJS Templates
Fix 'import '... completion for py3 & egg files....
Bradley M. Froehle -
Show More
@@ -1,331 +1,333 b''
1 """Implementations for various useful completers.
1 """Implementations for various useful completers.
2
2
3 These are all loaded by default by IPython.
3 These are all loaded by default by IPython.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2010-2011 The IPython Development Team.
6 # Copyright (C) 2010-2011 The IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the BSD License.
8 # Distributed under the terms of the BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib imports
18 # Stdlib imports
19 import glob
19 import glob
20 import imp
20 import imp
21 import inspect
21 import inspect
22 import os
22 import os
23 import re
23 import re
24 import sys
24 import sys
25
25
26 # Third-party imports
26 # Third-party imports
27 from time import time
27 from time import time
28 from zipimport import zipimporter
28 from zipimport import zipimporter
29
29
30 # Our own imports
30 # Our own imports
31 from IPython.core.completer import expand_user, compress_user
31 from IPython.core.completer import expand_user, compress_user
32 from IPython.core.error import TryNext
32 from IPython.core.error import TryNext
33 from IPython.utils import py3compat
33 from IPython.utils import py3compat
34 from IPython.utils._process_common import arg_split
34 from IPython.utils._process_common import arg_split
35
35
36 # FIXME: this should be pulled in with the right call via the component system
36 # FIXME: this should be pulled in with the right call via the component system
37 from IPython.core.ipapi import get as get_ipython
37 from IPython.core.ipapi import get as get_ipython
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # Globals and constants
40 # Globals and constants
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 # Time in seconds after which the rootmodules will be stored permanently in the
43 # Time in seconds after which the rootmodules will be stored permanently in the
44 # ipython ip.db database (kept in the user's .ipython dir).
44 # ipython ip.db database (kept in the user's .ipython dir).
45 TIMEOUT_STORAGE = 2
45 TIMEOUT_STORAGE = 2
46
46
47 # Time in seconds after which we give up
47 # Time in seconds after which we give up
48 TIMEOUT_GIVEUP = 20
48 TIMEOUT_GIVEUP = 20
49
49
50 # Regular expression for the python import statement
50 # Regular expression for the python import statement
51 import_re = re.compile(r'.*(\.so|\.py[cod]?)$')
51 import_re = re.compile(r'(?P<name>[a-zA-Z_][a-zA-Z0-9_]*?)'
52 r'(?P<package>[/\\]__init__)?'
53 r'(?P<suffix>%s)$' %
54 r'|'.join(re.escape(s[0]) for s in imp.get_suffixes()))
52
55
53 # RE for the ipython %run command (python + ipython scripts)
56 # RE for the ipython %run command (python + ipython scripts)
54 magic_run_re = re.compile(r'.*(\.ipy|\.py[w]?)$')
57 magic_run_re = re.compile(r'.*(\.ipy|\.py[w]?)$')
55
58
56 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
57 # Local utilities
60 # Local utilities
58 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
59
62
60 def module_list(path):
63 def module_list(path):
61 """
64 """
62 Return the list containing the names of the modules available in the given
65 Return the list containing the names of the modules available in the given
63 folder.
66 folder.
64 """
67 """
65 # sys.path has the cwd as an empty string, but isdir/listdir need it as '.'
68 # sys.path has the cwd as an empty string, but isdir/listdir need it as '.'
66 if path == '':
69 if path == '':
67 path = '.'
70 path = '.'
68
71
69 if os.path.isdir(path):
70 folder_list = os.listdir(path)
71 elif path.endswith('.egg'):
72 try:
73 folder_list = [f for f in zipimporter(path)._files]
74 except:
75 folder_list = []
76 else:
77 folder_list = []
78
79 if not folder_list:
80 return []
81
82 # A few local constants to be used in loops below
72 # A few local constants to be used in loops below
83 isfile = os.path.isfile
84 pjoin = os.path.join
73 pjoin = os.path.join
85 basename = os.path.basename
86
87 def is_importable_file(path):
88 """Returns True if the provided path is a valid importable module"""
89 name, extension = os.path.splitext( path )
90 return import_re.match(path) and py3compat.isidentifier(name)
91
74
92 # Now find actual path matches for packages or modules
75 if os.path.isdir(path):
93 folder_list = [p for p in folder_list
76 # Build a list of all files in the directory and all files
94 if any(isfile(pjoin(path, p, '__init__' + suffix[0])) for
77 # in its subdirectories. For performance reasons, do not
95 suffix in imp.get_suffixes())
78 # recurse more than one level into subdirectories.
96 or is_importable_file(p) ]
79 files = []
80 for root, dirs, nondirs in os.walk(path):
81 subdir = root[len(path)+1:]
82 if subdir:
83 files.extend(pjoin(subdir, f) for f in nondirs)
84 dirs[:] = [] # Do not recurse into additional subdirectories.
85 else:
86 files.extend(nondirs)
97
87
98 return [basename(p).split('.')[0] for p in folder_list]
88 else:
89 try:
90 files = list(zipimporter(path)._files.keys())
91 except:
92 files = []
93
94 # Build a list of modules which match the import_re regex.
95 modules = []
96 for f in files:
97 m = import_re.match(f)
98 if m:
99 modules.append(m.group('name'))
100 return list(set(modules))
99
101
100 def get_root_modules():
102 def get_root_modules():
101 """
103 """
102 Returns a list containing the names of all the modules available in the
104 Returns a list containing the names of all the modules available in the
103 folders of the pythonpath.
105 folders of the pythonpath.
104 """
106 """
105 ip = get_ipython()
107 ip = get_ipython()
106
108
107 if 'rootmodules' in ip.db:
109 if 'rootmodules' in ip.db:
108 return ip.db['rootmodules']
110 return ip.db['rootmodules']
109
111
110 t = time()
112 t = time()
111 store = False
113 store = False
112 modules = list(sys.builtin_module_names)
114 modules = list(sys.builtin_module_names)
113 for path in sys.path:
115 for path in sys.path:
114 modules += module_list(path)
116 modules += module_list(path)
115 if time() - t >= TIMEOUT_STORAGE and not store:
117 if time() - t >= TIMEOUT_STORAGE and not store:
116 store = True
118 store = True
117 print("\nCaching the list of root modules, please wait!")
119 print("\nCaching the list of root modules, please wait!")
118 print("(This will only be done once - type '%rehashx' to "
120 print("(This will only be done once - type '%rehashx' to "
119 "reset cache!)\n")
121 "reset cache!)\n")
120 sys.stdout.flush()
122 sys.stdout.flush()
121 if time() - t > TIMEOUT_GIVEUP:
123 if time() - t > TIMEOUT_GIVEUP:
122 print("This is taking too long, we give up.\n")
124 print("This is taking too long, we give up.\n")
123 ip.db['rootmodules'] = []
125 ip.db['rootmodules'] = []
124 return []
126 return []
125
127
126 modules = set(modules)
128 modules = set(modules)
127 if '__init__' in modules:
129 if '__init__' in modules:
128 modules.remove('__init__')
130 modules.remove('__init__')
129 modules = list(modules)
131 modules = list(modules)
130 if store:
132 if store:
131 ip.db['rootmodules'] = modules
133 ip.db['rootmodules'] = modules
132 return modules
134 return modules
133
135
134
136
135 def is_importable(module, attr, only_modules):
137 def is_importable(module, attr, only_modules):
136 if only_modules:
138 if only_modules:
137 return inspect.ismodule(getattr(module, attr))
139 return inspect.ismodule(getattr(module, attr))
138 else:
140 else:
139 return not(attr[:2] == '__' and attr[-2:] == '__')
141 return not(attr[:2] == '__' and attr[-2:] == '__')
140
142
141
143
142 def try_import(mod, only_modules=False):
144 def try_import(mod, only_modules=False):
143 try:
145 try:
144 m = __import__(mod)
146 m = __import__(mod)
145 except:
147 except:
146 return []
148 return []
147 mods = mod.split('.')
149 mods = mod.split('.')
148 for module in mods[1:]:
150 for module in mods[1:]:
149 m = getattr(m, module)
151 m = getattr(m, module)
150
152
151 m_is_init = hasattr(m, '__file__') and '__init__' in m.__file__
153 m_is_init = hasattr(m, '__file__') and '__init__' in m.__file__
152
154
153 completions = []
155 completions = []
154 if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init:
156 if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init:
155 completions.extend( [attr for attr in dir(m) if
157 completions.extend( [attr for attr in dir(m) if
156 is_importable(m, attr, only_modules)])
158 is_importable(m, attr, only_modules)])
157
159
158 completions.extend(getattr(m, '__all__', []))
160 completions.extend(getattr(m, '__all__', []))
159 if m_is_init:
161 if m_is_init:
160 completions.extend(module_list(os.path.dirname(m.__file__)))
162 completions.extend(module_list(os.path.dirname(m.__file__)))
161 completions = set(completions)
163 completions = set(completions)
162 if '__init__' in completions:
164 if '__init__' in completions:
163 completions.remove('__init__')
165 completions.remove('__init__')
164 return list(completions)
166 return list(completions)
165
167
166
168
167 #-----------------------------------------------------------------------------
169 #-----------------------------------------------------------------------------
168 # Completion-related functions.
170 # Completion-related functions.
169 #-----------------------------------------------------------------------------
171 #-----------------------------------------------------------------------------
170
172
171 def quick_completer(cmd, completions):
173 def quick_completer(cmd, completions):
172 """ Easily create a trivial completer for a command.
174 """ Easily create a trivial completer for a command.
173
175
174 Takes either a list of completions, or all completions in string (that will
176 Takes either a list of completions, or all completions in string (that will
175 be split on whitespace).
177 be split on whitespace).
176
178
177 Example::
179 Example::
178
180
179 [d:\ipython]|1> import ipy_completers
181 [d:\ipython]|1> import ipy_completers
180 [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz'])
182 [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz'])
181 [d:\ipython]|3> foo b<TAB>
183 [d:\ipython]|3> foo b<TAB>
182 bar baz
184 bar baz
183 [d:\ipython]|3> foo ba
185 [d:\ipython]|3> foo ba
184 """
186 """
185
187
186 if isinstance(completions, basestring):
188 if isinstance(completions, basestring):
187 completions = completions.split()
189 completions = completions.split()
188
190
189 def do_complete(self, event):
191 def do_complete(self, event):
190 return completions
192 return completions
191
193
192 get_ipython().set_hook('complete_command',do_complete, str_key = cmd)
194 get_ipython().set_hook('complete_command',do_complete, str_key = cmd)
193
195
194 def module_completion(line):
196 def module_completion(line):
195 """
197 """
196 Returns a list containing the completion possibilities for an import line.
198 Returns a list containing the completion possibilities for an import line.
197
199
198 The line looks like this :
200 The line looks like this :
199 'import xml.d'
201 'import xml.d'
200 'from xml.dom import'
202 'from xml.dom import'
201 """
203 """
202
204
203 words = line.split(' ')
205 words = line.split(' ')
204 nwords = len(words)
206 nwords = len(words)
205
207
206 # from whatever <tab> -> 'import '
208 # from whatever <tab> -> 'import '
207 if nwords == 3 and words[0] == 'from':
209 if nwords == 3 and words[0] == 'from':
208 return ['import ']
210 return ['import ']
209
211
210 # 'from xy<tab>' or 'import xy<tab>'
212 # 'from xy<tab>' or 'import xy<tab>'
211 if nwords < 3 and (words[0] in ['import','from']) :
213 if nwords < 3 and (words[0] in ['import','from']) :
212 if nwords == 1:
214 if nwords == 1:
213 return get_root_modules()
215 return get_root_modules()
214 mod = words[1].split('.')
216 mod = words[1].split('.')
215 if len(mod) < 2:
217 if len(mod) < 2:
216 return get_root_modules()
218 return get_root_modules()
217 completion_list = try_import('.'.join(mod[:-1]), True)
219 completion_list = try_import('.'.join(mod[:-1]), True)
218 return ['.'.join(mod[:-1] + [el]) for el in completion_list]
220 return ['.'.join(mod[:-1] + [el]) for el in completion_list]
219
221
220 # 'from xyz import abc<tab>'
222 # 'from xyz import abc<tab>'
221 if nwords >= 3 and words[0] == 'from':
223 if nwords >= 3 and words[0] == 'from':
222 mod = words[1]
224 mod = words[1]
223 return try_import(mod)
225 return try_import(mod)
224
226
225 #-----------------------------------------------------------------------------
227 #-----------------------------------------------------------------------------
226 # Completers
228 # Completers
227 #-----------------------------------------------------------------------------
229 #-----------------------------------------------------------------------------
228 # These all have the func(self, event) signature to be used as custom
230 # These all have the func(self, event) signature to be used as custom
229 # completers
231 # completers
230
232
231 def module_completer(self,event):
233 def module_completer(self,event):
232 """Give completions after user has typed 'import ...' or 'from ...'"""
234 """Give completions after user has typed 'import ...' or 'from ...'"""
233
235
234 # This works in all versions of python. While 2.5 has
236 # This works in all versions of python. While 2.5 has
235 # pkgutil.walk_packages(), that particular routine is fairly dangerous,
237 # pkgutil.walk_packages(), that particular routine is fairly dangerous,
236 # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full
238 # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full
237 # of possibly problematic side effects.
239 # of possibly problematic side effects.
238 # This search the folders in the sys.path for available modules.
240 # This search the folders in the sys.path for available modules.
239
241
240 return module_completion(event.line)
242 return module_completion(event.line)
241
243
242 # FIXME: there's a lot of logic common to the run, cd and builtin file
244 # FIXME: there's a lot of logic common to the run, cd and builtin file
243 # completers, that is currently reimplemented in each.
245 # completers, that is currently reimplemented in each.
244
246
245 def magic_run_completer(self, event):
247 def magic_run_completer(self, event):
246 """Complete files that end in .py or .ipy for the %run command.
248 """Complete files that end in .py or .ipy for the %run command.
247 """
249 """
248 comps = arg_split(event.line, strict=False)
250 comps = arg_split(event.line, strict=False)
249 relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"")
251 relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"")
250
252
251 #print("\nev=", event) # dbg
253 #print("\nev=", event) # dbg
252 #print("rp=", relpath) # dbg
254 #print("rp=", relpath) # dbg
253 #print('comps=', comps) # dbg
255 #print('comps=', comps) # dbg
254
256
255 lglob = glob.glob
257 lglob = glob.glob
256 isdir = os.path.isdir
258 isdir = os.path.isdir
257 relpath, tilde_expand, tilde_val = expand_user(relpath)
259 relpath, tilde_expand, tilde_val = expand_user(relpath)
258
260
259 dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)]
261 dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)]
260
262
261 # Find if the user has already typed the first filename, after which we
263 # Find if the user has already typed the first filename, after which we
262 # should complete on all files, since after the first one other files may
264 # should complete on all files, since after the first one other files may
263 # be arguments to the input script.
265 # be arguments to the input script.
264
266
265 if filter(magic_run_re.match, comps):
267 if filter(magic_run_re.match, comps):
266 pys = [f.replace('\\','/') for f in lglob('*')]
268 pys = [f.replace('\\','/') for f in lglob('*')]
267 else:
269 else:
268 pys = [f.replace('\\','/')
270 pys = [f.replace('\\','/')
269 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
271 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
270 lglob(relpath + '*.pyw')]
272 lglob(relpath + '*.pyw')]
271 #print('run comp:', dirs+pys) # dbg
273 #print('run comp:', dirs+pys) # dbg
272 return [compress_user(p, tilde_expand, tilde_val) for p in dirs+pys]
274 return [compress_user(p, tilde_expand, tilde_val) for p in dirs+pys]
273
275
274
276
275 def cd_completer(self, event):
277 def cd_completer(self, event):
276 """Completer function for cd, which only returns directories."""
278 """Completer function for cd, which only returns directories."""
277 ip = get_ipython()
279 ip = get_ipython()
278 relpath = event.symbol
280 relpath = event.symbol
279
281
280 #print(event) # dbg
282 #print(event) # dbg
281 if event.line.endswith('-b') or ' -b ' in event.line:
283 if event.line.endswith('-b') or ' -b ' in event.line:
282 # return only bookmark completions
284 # return only bookmark completions
283 bkms = self.db.get('bookmarks', None)
285 bkms = self.db.get('bookmarks', None)
284 if bkms:
286 if bkms:
285 return bkms.keys()
287 return bkms.keys()
286 else:
288 else:
287 return []
289 return []
288
290
289 if event.symbol == '-':
291 if event.symbol == '-':
290 width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
292 width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
291 # jump in directory history by number
293 # jump in directory history by number
292 fmt = '-%0' + width_dh +'d [%s]'
294 fmt = '-%0' + width_dh +'d [%s]'
293 ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
295 ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
294 if len(ents) > 1:
296 if len(ents) > 1:
295 return ents
297 return ents
296 return []
298 return []
297
299
298 if event.symbol.startswith('--'):
300 if event.symbol.startswith('--'):
299 return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']]
301 return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']]
300
302
301 # Expand ~ in path and normalize directory separators.
303 # Expand ~ in path and normalize directory separators.
302 relpath, tilde_expand, tilde_val = expand_user(relpath)
304 relpath, tilde_expand, tilde_val = expand_user(relpath)
303 relpath = relpath.replace('\\','/')
305 relpath = relpath.replace('\\','/')
304
306
305 found = []
307 found = []
306 for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
308 for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
307 if os.path.isdir(f)]:
309 if os.path.isdir(f)]:
308 if ' ' in d:
310 if ' ' in d:
309 # we don't want to deal with any of that, complex code
311 # we don't want to deal with any of that, complex code
310 # for this is elsewhere
312 # for this is elsewhere
311 raise TryNext
313 raise TryNext
312
314
313 found.append(d)
315 found.append(d)
314
316
315 if not found:
317 if not found:
316 if os.path.isdir(relpath):
318 if os.path.isdir(relpath):
317 return [compress_user(relpath, tilde_expand, tilde_val)]
319 return [compress_user(relpath, tilde_expand, tilde_val)]
318
320
319 # if no completions so far, try bookmarks
321 # if no completions so far, try bookmarks
320 bks = self.db.get('bookmarks',{}).iterkeys()
322 bks = self.db.get('bookmarks',{}).iterkeys()
321 bkmatches = [s for s in bks if s.startswith(event.symbol)]
323 bkmatches = [s for s in bks if s.startswith(event.symbol)]
322 if bkmatches:
324 if bkmatches:
323 return bkmatches
325 return bkmatches
324
326
325 raise TryNext
327 raise TryNext
326
328
327 return [compress_user(p, tilde_expand, tilde_val) for p in found]
329 return [compress_user(p, tilde_expand, tilde_val) for p in found]
328
330
329 def reset_completer(self, event):
331 def reset_completer(self, event):
330 "A completer for %reset magic"
332 "A completer for %reset magic"
331 return '-f -s in out array dhist'.split()
333 return '-f -s in out array dhist'.split()
General Comments 0
You need to be logged in to leave comments. Login now