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