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