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