-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
/
ipy_completers.py
400 lines (321 loc) · 12.5 KB
/
ipy_completers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
""" Implementations for various useful completers
See Extensions/ipy_stock_completers.py on examples of how to enable a completer,
but the basic idea is to do:
ip.set_hook('complete_command', svn_completer, str_key = 'svn')
"""
import IPython.ipapi
import glob,os,shlex,sys
import inspect
from time import time
from zipimport import zipimporter
ip = IPython.ipapi.get()
try:
set
except:
from sets import Set as set
TIMEOUT_STORAGE = 3 #Time in seconds after which the rootmodules will be stored
TIMEOUT_GIVEUP = 20 #Time in seconds after which we give up
def quick_completer(cmd, completions):
""" Easily create a trivial completer for a command.
Takes either a list of completions, or all completions in string
(that will be split on whitespace)
Example::
[d:\ipython]|1> import ipy_completers
[d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz'])
[d:\ipython]|3> foo b<TAB>
bar baz
[d:\ipython]|3> foo ba
"""
if isinstance(completions, basestring):
completions = completions.split()
def do_complete(self,event):
return completions
ip.set_hook('complete_command',do_complete, str_key = cmd)
def getRootModules():
"""
Returns a list containing the names of all the modules available in the
folders of the pythonpath.
"""
modules = []
if ip.db.has_key('rootmodules'):
return ip.db['rootmodules']
t = time()
store = False
for path in sys.path:
modules += moduleList(path)
if time() - t >= TIMEOUT_STORAGE and not store:
store = True
print "\nCaching the list of root modules, please wait!"
print "(This will only be done once - type '%rehashx' to " + \
"reset cache!)"
print
if time() - t > TIMEOUT_GIVEUP:
print "This is taking too long, we give up."
print
ip.db['rootmodules'] = []
return []
modules += sys.builtin_module_names
modules = list(set(modules))
if '__init__' in modules:
modules.remove('__init__')
modules = list(set(modules))
if store:
ip.db['rootmodules'] = modules
return modules
def moduleList(path):
"""
Return the list containing the names of the modules available in the given
folder.
"""
if os.path.isdir(path):
folder_list = os.listdir(path)
elif path.endswith('.egg'):
try:
folder_list = [f for f in zipimporter(path)._files]
except:
folder_list = []
else:
folder_list = []
#folder_list = glob.glob(os.path.join(path,'*'))
folder_list = [p for p in folder_list \
if os.path.exists(os.path.join(path, p,'__init__.py'))\
or p[-3:] in ('.py','.so')\
or p[-4:] in ('.pyc','.pyo','.pyd')]
folder_list = [os.path.basename(p).split('.')[0] for p in folder_list]
return folder_list
def moduleCompletion(line):
"""
Returns a list containing the completion possibilities for an import line.
The line looks like this :
'import xml.d'
'from xml.dom import'
"""
def tryImport(mod, only_modules=False):
def isImportable(module, attr):
if only_modules:
return inspect.ismodule(getattr(module, attr))
else:
return not(attr[:2] == '__' and attr[-2:] == '__')
try:
m = __import__(mod)
except:
return []
mods = mod.split('.')
for module in mods[1:]:
m = getattr(m,module)
if (not hasattr(m, '__file__')) or (not only_modules) or\
(hasattr(m, '__file__') and '__init__' in m.__file__):
completion_list = [attr for attr in dir(m) if isImportable(m, attr)]
completion_list.extend(getattr(m,'__all__',[]))
if hasattr(m, '__file__') and '__init__' in m.__file__:
completion_list.extend(moduleList(os.path.dirname(m.__file__)))
completion_list = list(set(completion_list))
if '__init__' in completion_list:
completion_list.remove('__init__')
return completion_list
words = line.split(' ')
if len(words) == 3 and words[0] == 'from':
return ['import ']
if len(words) < 3 and (words[0] in ['import','from']) :
if len(words) == 1:
return getRootModules()
mod = words[1].split('.')
if len(mod) < 2:
return getRootModules()
completion_list = tryImport('.'.join(mod[:-1]), True)
completion_list = ['.'.join(mod[:-1] + [el]) for el in completion_list]
return completion_list
if len(words) >= 3 and words[0] == 'from':
mod = words[1]
return tryImport(mod)
def vcs_completer(commands, event):
""" utility to make writing typical version control app completers easier
VCS command line apps typically have the format:
[sudo ]PROGNAME [help] [command] file file...
"""
cmd_param = event.line.split()
if event.line.endswith(' '):
cmd_param.append('')
if cmd_param[0] == 'sudo':
cmd_param = cmd_param[1:]
if len(cmd_param) == 2 or 'help' in cmd_param:
return commands.split()
return ip.IP.Completer.file_matches(event.symbol)
pkg_cache = None
def module_completer(self,event):
""" Give completions after user has typed 'import ...' or 'from ...'"""
# This works in all versions of python. While 2.5 has
# pkgutil.walk_packages(), that particular routine is fairly dangerous,
# since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full
# of possibly problematic side effects.
# This search the folders in the sys.path for available modules.
return moduleCompletion(event.line)
svn_commands = """\
add blame praise annotate ann cat checkout co cleanup commit ci copy
cp delete del remove rm diff di export help ? h import info list ls
lock log merge mkdir move mv rename ren propdel pdel pd propedit pedit
pe propget pget pg proplist plist pl propset pset ps resolved revert
status stat st switch sw unlock update
"""
def svn_completer(self,event):
return vcs_completer(svn_commands, event)
hg_commands = """
add addremove annotate archive backout branch branches bundle cat
clone commit copy diff export grep heads help identify import incoming
init locate log manifest merge outgoing parents paths pull push
qapplied qclone qcommit qdelete qdiff qfold qguard qheader qimport
qinit qnew qnext qpop qprev qpush qrefresh qrename qrestore qsave
qselect qseries qtop qunapplied recover remove rename revert rollback
root serve showconfig status strip tag tags tip unbundle update verify
version
"""
def hg_completer(self,event):
""" Completer for mercurial commands """
return vcs_completer(hg_commands, event)
__bzr_commands = None
def bzr_commands():
global __bzr_commands
if __bzr_commands is not None:
return __bzr_commands
out = os.popen('bzr help commands')
__bzr_commands = [l.split()[0] for l in out]
return __bzr_commands
def bzr_completer(self,event):
""" Completer for bazaar commands """
cmd_param = event.line.split()
if event.line.endswith(' '):
cmd_param.append('')
if len(cmd_param) > 2:
cmd = cmd_param[1]
param = cmd_param[-1]
output_file = (param == '--output=')
if cmd == 'help':
return bzr_commands()
elif cmd in ['bundle-revisions','conflicts',
'deleted','nick','register-branch',
'serve','unbind','upgrade','version',
'whoami'] and not output_file:
return []
else:
# the rest are probably file names
return ip.IP.Completer.file_matches(event.symbol)
return bzr_commands()
def shlex_split(x):
"""Helper function to split lines into segments."""
#shlex.split raise exception if syntax error in sh syntax
#for example if no closing " is found. This function keeps dropping
#the last character of the line until shlex.split does not raise
#exception. Adds end of the line to the result of shlex.split
#example: %run "c:/python -> ['%run','"c:/python']
endofline=[]
while x!="":
try:
comps=shlex.split(x)
if len(endofline)>=1:
comps.append("".join(endofline))
return comps
except ValueError:
endofline=[x[-1:]]+endofline
x=x[:-1]
return ["".join(endofline)]
def runlistpy(self, event):
comps = shlex_split(event.line)
relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"")
#print "\nev=",event # dbg
#print "rp=",relpath # dbg
#print 'comps=',comps # dbg
lglob = glob.glob
isdir = os.path.isdir
if relpath.startswith('~'):
relpath = os.path.expanduser(relpath)
dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*')
if isdir(f)]
# Find if the user has already typed the first filename, after which we
# should complete on all files, since after the first one other files may
# be arguments to the input script.
#filter(
if filter(lambda f: f.endswith('.py') or f.endswith('.ipy') or
f.endswith('.pyw'),comps):
pys = [f.replace('\\','/') for f in lglob('*')]
else:
pys = [f.replace('\\','/')
for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
lglob(relpath + '*.pyw')]
return dirs + pys
greedy_cd_completer = False
def cd_completer(self, event):
relpath = event.symbol
#print event # dbg
if '-b' in event.line:
# return only bookmark completions
bkms = self.db.get('bookmarks',{})
return bkms.keys()
if event.symbol == '-':
width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
# jump in directory history by number
fmt = '-%0' + width_dh +'d [%s]'
ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
if len(ents) > 1:
return ents
return []
if event.symbol.startswith('--'):
return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']]
if relpath.startswith('~'):
relpath = os.path.expanduser(relpath).replace('\\','/')
found = []
for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
if os.path.isdir(f)]:
if ' ' in d:
# we don't want to deal with any of that, complex code
# for this is elsewhere
raise IPython.ipapi.TryNext
found.append( d )
if not found:
if os.path.isdir(relpath):
return [relpath]
# if no completions so far, try bookmarks
bks = self.db.get('bookmarks',{}).keys()
bkmatches = [s for s in bks if s.startswith(event.symbol)]
if bkmatches:
return bkmatches
raise IPython.ipapi.TryNext
def single_dir_expand(matches):
"Recursively expand match lists containing a single dir."
if len(matches) == 1 and os.path.isdir(matches[0]):
# Takes care of links to directories also. Use '/'
# explicitly, even under Windows, so that name completions
# don't end up escaped.
d = matches[0]
if d[-1] in ['/','\\']:
d = d[:-1]
subdirs = [p for p in os.listdir(d) if os.path.isdir( d + '/' + p) and not p.startswith('.')]
if subdirs:
matches = [ (d + '/' + p) for p in subdirs ]
return single_dir_expand(matches)
else:
return matches
else:
return matches
if greedy_cd_completer:
return single_dir_expand(found)
else:
return found
def apt_get_packages(prefix):
out = os.popen('apt-cache pkgnames')
for p in out:
if p.startswith(prefix):
yield p.rstrip()
apt_commands = """\
update upgrade install remove purge source build-dep dist-upgrade
dselect-upgrade clean autoclean check"""
def apt_completer(self, event):
""" Completer for apt-get (uses apt-cache internally)
"""
cmd_param = event.line.split()
if event.line.endswith(' '):
cmd_param.append('')
if cmd_param[0] == 'sudo':
cmd_param = cmd_param[1:]
if len(cmd_param) == 2 or 'help' in cmd_param:
return apt_commands.split()
return list(apt_get_packages(event.symbol))