##// END OF EJS Templates
Upstream merges.
gvaroquaux -
r1298:92012389 merge
parent child Browse files
Show More
@@ -1,386 +1,391 b''
1
1
2 """ Implementations for various useful completers
2 """ Implementations for various useful completers
3
3
4 See Extensions/ipy_stock_completers.py on examples of how to enable a completer,
4 See Extensions/ipy_stock_completers.py on examples of how to enable a completer,
5 but the basic idea is to do:
5 but the basic idea is to do:
6
6
7 ip.set_hook('complete_command', svn_completer, str_key = 'svn')
7 ip.set_hook('complete_command', svn_completer, str_key = 'svn')
8
8
9 """
9 """
10 import IPython.ipapi
10 import IPython.ipapi
11 import glob,os,shlex,sys
11 import glob,os,shlex,sys
12 import inspect
12 import inspect
13 from time import time
13 from time import time
14 from zipimport import zipimporter
14 from zipimport import zipimporter
15 ip = IPython.ipapi.get()
15 ip = IPython.ipapi.get()
16
16
17 try:
17 try:
18 set
18 set
19 except:
19 except:
20 from sets import Set as set
20 from sets import Set as set
21
21
22 TIMEOUT_STORAGE = 3 #Time in seconds after which the rootmodules will be stored
22 TIMEOUT_STORAGE = 3 #Time in seconds after which the rootmodules will be stored
23 TIMEOUT_GIVEUP = 20 #Time in seconds after which we give up
23 TIMEOUT_GIVEUP = 20 #Time in seconds after which we give up
24
24
25 def quick_completer(cmd, completions):
25 def quick_completer(cmd, completions):
26 """ Easily create a trivial completer for a command.
26 """ Easily create a trivial completer for a command.
27
27
28 Takes either a list of completions, or all completions in string
28 Takes either a list of completions, or all completions in string
29 (that will be split on whitespace)
29 (that will be split on whitespace)
30
30
31 Example::
31 Example::
32
32
33 [d:\ipython]|1> import ipy_completers
33 [d:\ipython]|1> import ipy_completers
34 [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz'])
34 [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz'])
35 [d:\ipython]|3> foo b<TAB>
35 [d:\ipython]|3> foo b<TAB>
36 bar baz
36 bar baz
37 [d:\ipython]|3> foo ba
37 [d:\ipython]|3> foo ba
38 """
38 """
39 if isinstance(completions, basestring):
39 if isinstance(completions, basestring):
40
40
41 completions = completions.split()
41 completions = completions.split()
42 def do_complete(self,event):
42 def do_complete(self,event):
43 return completions
43 return completions
44
44
45 ip.set_hook('complete_command',do_complete, str_key = cmd)
45 ip.set_hook('complete_command',do_complete, str_key = cmd)
46
46
47 def getRootModules():
47 def getRootModules():
48 """
48 """
49 Returns a list containing the names of all the modules available in the
49 Returns a list containing the names of all the modules available in the
50 folders of the pythonpath.
50 folders of the pythonpath.
51 """
51 """
52 modules = []
52 modules = []
53 if ip.db.has_key('rootmodules'):
53 if ip.db.has_key('rootmodules'):
54 return ip.db['rootmodules']
54 return ip.db['rootmodules']
55 t = time()
55 t = time()
56 store = False
56 store = False
57 for path in sys.path:
57 for path in sys.path:
58 modules += moduleList(path)
58 modules += moduleList(path)
59 if time() - t >= TIMEOUT_STORAGE and not store:
59 if time() - t >= TIMEOUT_STORAGE and not store:
60 store = True
60 store = True
61 print "\nCaching the list of root modules, please wait!"
61 print "\nCaching the list of root modules, please wait!"
62 print "(This will only be done once - type '%rehashx' to " + \
62 print "(This will only be done once - type '%rehashx' to " + \
63 "reset cache!)"
63 "reset cache!)"
64 print
64 print
65 if time() - t > TIMEOUT_GIVEUP:
65 if time() - t > TIMEOUT_GIVEUP:
66 print "This is taking too long, we give up."
66 print "This is taking too long, we give up."
67 print
67 print
68 ip.db['rootmodules'] = []
68 ip.db['rootmodules'] = []
69 return []
69 return []
70
70
71 modules += sys.builtin_module_names
71 modules += sys.builtin_module_names
72
72
73 modules = list(set(modules))
73 modules = list(set(modules))
74 if '__init__' in modules:
74 if '__init__' in modules:
75 modules.remove('__init__')
75 modules.remove('__init__')
76 modules = list(set(modules))
76 modules = list(set(modules))
77 if store:
77 if store:
78 ip.db['rootmodules'] = modules
78 ip.db['rootmodules'] = modules
79 return modules
79 return modules
80
80
81 def moduleList(path):
81 def moduleList(path):
82 """
82 """
83 Return the list containing the names of the modules available in the given
83 Return the list containing the names of the modules available in the given
84 folder.
84 folder.
85 """
85 """
86
86
87 if os.path.isdir(path):
87 if os.path.isdir(path):
88 folder_list = os.listdir(path)
88 folder_list = os.listdir(path)
89 elif path.endswith('.egg'):
89 elif path.endswith('.egg'):
90 try:
90 try:
91 folder_list = [f for f in zipimporter(path)._files]
91 folder_list = [f for f in zipimporter(path)._files]
92 except:
92 except:
93 folder_list = []
93 folder_list = []
94 else:
94 else:
95 folder_list = []
95 folder_list = []
96 #folder_list = glob.glob(os.path.join(path,'*'))
96 #folder_list = glob.glob(os.path.join(path,'*'))
97 folder_list = [p for p in folder_list \
97 folder_list = [p for p in folder_list \
98 if os.path.exists(os.path.join(path, p,'__init__.py'))\
98 if os.path.exists(os.path.join(path, p,'__init__.py'))\
99 or p[-3:] in ('.py','.so')\
99 or p[-3:] in ('.py','.so')\
100 or p[-4:] in ('.pyc','.pyo','.pyd')]
100 or p[-4:] in ('.pyc','.pyo','.pyd')]
101
101
102 folder_list = [os.path.basename(p).split('.')[0] for p in folder_list]
102 folder_list = [os.path.basename(p).split('.')[0] for p in folder_list]
103 return folder_list
103 return folder_list
104
104
105 def moduleCompletion(line):
105 def moduleCompletion(line):
106 """
106 """
107 Returns a list containing the completion possibilities for an import line.
107 Returns a list containing the completion possibilities for an import line.
108 The line looks like this :
108 The line looks like this :
109 'import xml.d'
109 'import xml.d'
110 'from xml.dom import'
110 'from xml.dom import'
111 """
111 """
112 def tryImport(mod, only_modules=False):
112 def tryImport(mod, only_modules=False):
113 def isImportable(module, attr):
113 def isImportable(module, attr):
114 if only_modules:
114 if only_modules:
115 return inspect.ismodule(getattr(module, attr))
115 return inspect.ismodule(getattr(module, attr))
116 else:
116 else:
117 return not(attr[:2] == '__' and attr[-2:] == '__')
117 return not(attr[:2] == '__' and attr[-2:] == '__')
118 try:
118 try:
119 m = __import__(mod)
119 m = __import__(mod)
120 except:
120 except:
121 return []
121 return []
122 mods = mod.split('.')
122 mods = mod.split('.')
123 for module in mods[1:]:
123 for module in mods[1:]:
124 m = getattr(m,module)
124 m = getattr(m,module)
125 if (not hasattr(m, '__file__')) or (not only_modules) or\
125 if (not hasattr(m, '__file__')) or (not only_modules) or\
126 (hasattr(m, '__file__') and '__init__' in m.__file__):
126 (hasattr(m, '__file__') and '__init__' in m.__file__):
127 completion_list = [attr for attr in dir(m) if isImportable(m, attr)]
127 completion_list = [attr for attr in dir(m) if isImportable(m, attr)]
128 completion_list.extend(getattr(m,'__all__',[]))
128 completion_list.extend(getattr(m,'__all__',[]))
129 if hasattr(m, '__file__') and '__init__' in m.__file__:
129 if hasattr(m, '__file__') and '__init__' in m.__file__:
130 completion_list.extend(moduleList(os.path.dirname(m.__file__)))
130 completion_list.extend(moduleList(os.path.dirname(m.__file__)))
131 completion_list = list(set(completion_list))
131 completion_list = list(set(completion_list))
132 if '__init__' in completion_list:
132 if '__init__' in completion_list:
133 completion_list.remove('__init__')
133 completion_list.remove('__init__')
134 return completion_list
134 return completion_list
135
135
136 words = line.split(' ')
136 words = line.split(' ')
137 if len(words) == 3 and words[0] == 'from':
137 if len(words) == 3 and words[0] == 'from':
138 return ['import ']
138 return ['import ']
139 if len(words) < 3 and (words[0] in ['import','from']) :
139 if len(words) < 3 and (words[0] in ['import','from']) :
140 if len(words) == 1:
140 if len(words) == 1:
141 return getRootModules()
141 return getRootModules()
142 mod = words[1].split('.')
142 mod = words[1].split('.')
143 if len(mod) < 2:
143 if len(mod) < 2:
144 return getRootModules()
144 return getRootModules()
145 completion_list = tryImport('.'.join(mod[:-1]), True)
145 completion_list = tryImport('.'.join(mod[:-1]), True)
146 completion_list = ['.'.join(mod[:-1] + [el]) for el in completion_list]
146 completion_list = ['.'.join(mod[:-1] + [el]) for el in completion_list]
147 return completion_list
147 return completion_list
148 if len(words) >= 3 and words[0] == 'from':
148 if len(words) >= 3 and words[0] == 'from':
149 mod = words[1]
149 mod = words[1]
150 return tryImport(mod)
150 return tryImport(mod)
151
151
152 def vcs_completer(commands, event):
152 def vcs_completer(commands, event):
153 """ utility to make writing typical version control app completers easier
153 """ utility to make writing typical version control app completers easier
154
154
155 VCS command line apps typically have the format:
155 VCS command line apps typically have the format:
156
156
157 [sudo ]PROGNAME [help] [command] file file...
157 [sudo ]PROGNAME [help] [command] file file...
158
158
159 """
159 """
160
160
161
161
162 cmd_param = event.line.split()
162 cmd_param = event.line.split()
163 if event.line.endswith(' '):
163 if event.line.endswith(' '):
164 cmd_param.append('')
164 cmd_param.append('')
165
165
166 if cmd_param[0] == 'sudo':
166 if cmd_param[0] == 'sudo':
167 cmd_param = cmd_param[1:]
167 cmd_param = cmd_param[1:]
168
168
169 if len(cmd_param) == 2 or 'help' in cmd_param:
169 if len(cmd_param) == 2 or 'help' in cmd_param:
170 return commands.split()
170 return commands.split()
171
171
172 return ip.IP.Completer.file_matches(event.symbol)
172 return ip.IP.Completer.file_matches(event.symbol)
173
173
174
174
175 pkg_cache = None
175 pkg_cache = None
176
176
177 def module_completer(self,event):
177 def module_completer(self,event):
178 """ Give completions after user has typed 'import ...' or 'from ...'"""
178 """ Give completions after user has typed 'import ...' or 'from ...'"""
179
179
180 # This works in all versions of python. While 2.5 has
180 # This works in all versions of python. While 2.5 has
181 # pkgutil.walk_packages(), that particular routine is fairly dangerous,
181 # pkgutil.walk_packages(), that particular routine is fairly dangerous,
182 # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full
182 # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full
183 # of possibly problematic side effects.
183 # of possibly problematic side effects.
184 # This search the folders in the sys.path for available modules.
184 # This search the folders in the sys.path for available modules.
185
185
186 return moduleCompletion(event.line)
186 return moduleCompletion(event.line)
187
187
188
188
189 svn_commands = """\
189 svn_commands = """\
190 add blame praise annotate ann cat checkout co cleanup commit ci copy
190 add blame praise annotate ann cat checkout co cleanup commit ci copy
191 cp delete del remove rm diff di export help ? h import info list ls
191 cp delete del remove rm diff di export help ? h import info list ls
192 lock log merge mkdir move mv rename ren propdel pdel pd propedit pedit
192 lock log merge mkdir move mv rename ren propdel pdel pd propedit pedit
193 pe propget pget pg proplist plist pl propset pset ps resolved revert
193 pe propget pget pg proplist plist pl propset pset ps resolved revert
194 status stat st switch sw unlock update
194 status stat st switch sw unlock update
195 """
195 """
196
196
197 def svn_completer(self,event):
197 def svn_completer(self,event):
198 return vcs_completer(svn_commands, event)
198 return vcs_completer(svn_commands, event)
199
199
200
200
201 hg_commands = """
201 hg_commands = """
202 add addremove annotate archive backout branch branches bundle cat
202 add addremove annotate archive backout branch branches bundle cat
203 clone commit copy diff export grep heads help identify import incoming
203 clone commit copy diff export grep heads help identify import incoming
204 init locate log manifest merge outgoing parents paths pull push
204 init locate log manifest merge outgoing parents paths pull push
205 qapplied qclone qcommit qdelete qdiff qfold qguard qheader qimport
205 qapplied qclone qcommit qdelete qdiff qfold qguard qheader qimport
206 qinit qnew qnext qpop qprev qpush qrefresh qrename qrestore qsave
206 qinit qnew qnext qpop qprev qpush qrefresh qrename qrestore qsave
207 qselect qseries qtop qunapplied recover remove rename revert rollback
207 qselect qseries qtop qunapplied recover remove rename revert rollback
208 root serve showconfig status strip tag tags tip unbundle update verify
208 root serve showconfig status strip tag tags tip unbundle update verify
209 version
209 version
210 """
210 """
211
211
212 def hg_completer(self,event):
212 def hg_completer(self,event):
213 """ Completer for mercurial commands """
213 """ Completer for mercurial commands """
214
214
215 return vcs_completer(hg_commands, event)
215 return vcs_completer(hg_commands, event)
216
216
217
217
218
218
219 __bzr_commands = None
219 __bzr_commands = None
220
220
221 def bzr_commands():
221 def bzr_commands():
222 global __bzr_commands
222 global __bzr_commands
223 if __bzr_commands is not None:
223 if __bzr_commands is not None:
224 return __bzr_commands
224 return __bzr_commands
225 out = os.popen('bzr help commands')
225 out = os.popen('bzr help commands')
226 __bzr_commands = [l.split()[0] for l in out]
226 __bzr_commands = [l.split()[0] for l in out]
227 return __bzr_commands
227 return __bzr_commands
228
228
229 def bzr_completer(self,event):
229 def bzr_completer(self,event):
230 """ Completer for bazaar commands """
230 """ Completer for bazaar commands """
231 cmd_param = event.line.split()
231 cmd_param = event.line.split()
232 if event.line.endswith(' '):
232 if event.line.endswith(' '):
233 cmd_param.append('')
233 cmd_param.append('')
234
234
235 if len(cmd_param) > 2:
235 if len(cmd_param) > 2:
236 cmd = cmd_param[1]
236 cmd = cmd_param[1]
237 param = cmd_param[-1]
237 param = cmd_param[-1]
238 output_file = (param == '--output=')
238 output_file = (param == '--output=')
239 if cmd == 'help':
239 if cmd == 'help':
240 return bzr_commands()
240 return bzr_commands()
241 elif cmd in ['bundle-revisions','conflicts',
241 elif cmd in ['bundle-revisions','conflicts',
242 'deleted','nick','register-branch',
242 'deleted','nick','register-branch',
243 'serve','unbind','upgrade','version',
243 'serve','unbind','upgrade','version',
244 'whoami'] and not output_file:
244 'whoami'] and not output_file:
245 return []
245 return []
246 else:
246 else:
247 # the rest are probably file names
247 # the rest are probably file names
248 return ip.IP.Completer.file_matches(event.symbol)
248 return ip.IP.Completer.file_matches(event.symbol)
249
249
250 return bzr_commands()
250 return bzr_commands()
251
251
252
252
253 def shlex_split(x):
253 def shlex_split(x):
254 """Helper function to split lines into segments."""
254 """Helper function to split lines into segments."""
255 #shlex.split raise exception if syntax error in sh syntax
255 #shlex.split raise exception if syntax error in sh syntax
256 #for example if no closing " is found. This function keeps dropping
256 #for example if no closing " is found. This function keeps dropping
257 #the last character of the line until shlex.split does not raise
257 #the last character of the line until shlex.split does not raise
258 #exception. Adds end of the line to the result of shlex.split
258 #exception. Adds end of the line to the result of shlex.split
259 #example: %run "c:/python -> ['%run','"c:/python']
259 #example: %run "c:/python -> ['%run','"c:/python']
260 endofline=[]
260 endofline=[]
261 while x!="":
261 while x!="":
262 try:
262 try:
263 comps=shlex.split(x)
263 comps=shlex.split(x)
264 if len(endofline)>=1:
264 if len(endofline)>=1:
265 comps.append("".join(endofline))
265 comps.append("".join(endofline))
266 return comps
266 return comps
267 except ValueError:
267 except ValueError:
268 endofline=[x[-1:]]+endofline
268 endofline=[x[-1:]]+endofline
269 x=x[:-1]
269 x=x[:-1]
270 return ["".join(endofline)]
270 return ["".join(endofline)]
271
271
272 def runlistpy(self, event):
272 def runlistpy(self, event):
273 comps = shlex_split(event.line)
273 comps = shlex_split(event.line)
274 relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"")
274 relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"")
275
275
276 #print "\nev=",event # dbg
276 #print "\nev=",event # dbg
277 #print "rp=",relpath # dbg
277 #print "rp=",relpath # dbg
278 #print 'comps=',comps # dbg
278 #print 'comps=',comps # dbg
279
279
280 lglob = glob.glob
280 lglob = glob.glob
281 isdir = os.path.isdir
281 isdir = os.path.isdir
282 if relpath.startswith('~'):
282 if relpath.startswith('~'):
283 relpath = os.path.expanduser(relpath)
283 relpath = os.path.expanduser(relpath)
284 dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*')
284 dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*')
285 if isdir(f)]
285 if isdir(f)]
286
286
287 # Find if the user has already typed the first filename, after which we
287 # Find if the user has already typed the first filename, after which we
288 # should complete on all files, since after the first one other files may
288 # should complete on all files, since after the first one other files may
289 # be arguments to the input script.
289 # be arguments to the input script.
290 #filter(
290 #filter(
291 if filter(lambda f: f.endswith('.py') or f.endswith('.ipy') or
291 if filter(lambda f: f.endswith('.py') or f.endswith('.ipy') or
292 f.endswith('.pyw'),comps):
292 f.endswith('.pyw'),comps):
293 pys = [f.replace('\\','/') for f in lglob('*')]
293 pys = [f.replace('\\','/') for f in lglob('*')]
294 else:
294 else:
295 pys = [f.replace('\\','/')
295 pys = [f.replace('\\','/')
296 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
296 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
297 lglob(relpath + '*.pyw')]
297 lglob(relpath + '*.pyw')]
298 return dirs + pys
298 return dirs + pys
299
299
300
300
301 greedy_cd_completer = False
302
301 def cd_completer(self, event):
303 def cd_completer(self, event):
302 relpath = event.symbol
304 relpath = event.symbol
303 #print event # dbg
305 #print event # dbg
304 if '-b' in event.line:
306 if '-b' in event.line:
305 # return only bookmark completions
307 # return only bookmark completions
306 bkms = self.db.get('bookmarks',{})
308 bkms = self.db.get('bookmarks',{})
307 return bkms.keys()
309 return bkms.keys()
308
310
309
311
310 if event.symbol == '-':
312 if event.symbol == '-':
311 width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
313 width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
312 # jump in directory history by number
314 # jump in directory history by number
313 fmt = '-%0' + width_dh +'d [%s]'
315 fmt = '-%0' + width_dh +'d [%s]'
314 ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
316 ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
315 if len(ents) > 1:
317 if len(ents) > 1:
316 return ents
318 return ents
317 return []
319 return []
318
320
319 if relpath.startswith('~'):
321 if relpath.startswith('~'):
320 relpath = os.path.expanduser(relpath).replace('\\','/')
322 relpath = os.path.expanduser(relpath).replace('\\','/')
321 found = []
323 found = []
322 for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
324 for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
323 if os.path.isdir(f)]:
325 if os.path.isdir(f)]:
324 if ' ' in d:
326 if ' ' in d:
325 # we don't want to deal with any of that, complex code
327 # we don't want to deal with any of that, complex code
326 # for this is elsewhere
328 # for this is elsewhere
327 raise IPython.ipapi.TryNext
329 raise IPython.ipapi.TryNext
328 found.append( d )
330 found.append( d )
329
331
330 if not found:
332 if not found:
331 if os.path.isdir(relpath):
333 if os.path.isdir(relpath):
332 return [relpath]
334 return [relpath]
333 raise IPython.ipapi.TryNext
335 raise IPython.ipapi.TryNext
334
336
335
337
336 def single_dir_expand(matches):
338 def single_dir_expand(matches):
337 "Recursively expand match lists containing a single dir."
339 "Recursively expand match lists containing a single dir."
338
340
339 if len(matches) == 1 and os.path.isdir(matches[0]):
341 if len(matches) == 1 and os.path.isdir(matches[0]):
340 # Takes care of links to directories also. Use '/'
342 # Takes care of links to directories also. Use '/'
341 # explicitly, even under Windows, so that name completions
343 # explicitly, even under Windows, so that name completions
342 # don't end up escaped.
344 # don't end up escaped.
343 d = matches[0]
345 d = matches[0]
344 if d[-1] in ['/','\\']:
346 if d[-1] in ['/','\\']:
345 d = d[:-1]
347 d = d[:-1]
346
348
347 subdirs = [p for p in os.listdir(d) if os.path.isdir( d + '/' + p) and not p.startswith('.')]
349 subdirs = [p for p in os.listdir(d) if os.path.isdir( d + '/' + p) and not p.startswith('.')]
348 if subdirs:
350 if subdirs:
349 matches = [ (d + '/' + p) for p in subdirs ]
351 matches = [ (d + '/' + p) for p in subdirs ]
350 return single_dir_expand(matches)
352 return single_dir_expand(matches)
351 else:
353 else:
352 return matches
354 return matches
353 else:
355 else:
354 return matches
356 return matches
355
357
356 return single_dir_expand(found)
358 if greedy_cd_completer:
359 return single_dir_expand(found)
360 else:
361 return found
357
362
358 def apt_get_packages(prefix):
363 def apt_get_packages(prefix):
359 out = os.popen('apt-cache pkgnames')
364 out = os.popen('apt-cache pkgnames')
360 for p in out:
365 for p in out:
361 if p.startswith(prefix):
366 if p.startswith(prefix):
362 yield p.rstrip()
367 yield p.rstrip()
363
368
364
369
365 apt_commands = """\
370 apt_commands = """\
366 update upgrade install remove purge source build-dep dist-upgrade
371 update upgrade install remove purge source build-dep dist-upgrade
367 dselect-upgrade clean autoclean check"""
372 dselect-upgrade clean autoclean check"""
368
373
369 def apt_completer(self, event):
374 def apt_completer(self, event):
370 """ Completer for apt-get (uses apt-cache internally)
375 """ Completer for apt-get (uses apt-cache internally)
371
376
372 """
377 """
373
378
374
379
375 cmd_param = event.line.split()
380 cmd_param = event.line.split()
376 if event.line.endswith(' '):
381 if event.line.endswith(' '):
377 cmd_param.append('')
382 cmd_param.append('')
378
383
379 if cmd_param[0] == 'sudo':
384 if cmd_param[0] == 'sudo':
380 cmd_param = cmd_param[1:]
385 cmd_param = cmd_param[1:]
381
386
382 if len(cmd_param) == 2 or 'help' in cmd_param:
387 if len(cmd_param) == 2 or 'help' in cmd_param:
383 return apt_commands.split()
388 return apt_commands.split()
384
389
385 return list(apt_get_packages(event.symbol))
390 return list(apt_get_packages(event.symbol))
386
391
@@ -1,251 +1,258 b''
1 """Shell mode for IPython.
1 """Shell mode for IPython.
2
2
3 Start ipython in shell mode by invoking "ipython -p sh"
3 Start ipython in shell mode by invoking "ipython -p sh"
4
4
5 (the old version, "ipython -p pysh" still works but this is the more "modern"
5 (the old version, "ipython -p pysh" still works but this is the more "modern"
6 shell mode and is recommended for users who don't care about pysh-mode
6 shell mode and is recommended for users who don't care about pysh-mode
7 compatibility)
7 compatibility)
8 """
8 """
9
9
10 from IPython import ipapi
10 from IPython import ipapi
11 import os,textwrap
11 import os,textwrap
12
12
13 # The import below effectively obsoletes your old-style ipythonrc[.ini],
13 # The import below effectively obsoletes your old-style ipythonrc[.ini],
14 # so consider yourself warned!
14 # so consider yourself warned!
15
15
16 import ipy_defaults
16 import ipy_defaults
17
17
18 def main():
18 def main():
19 ip = ipapi.get()
19 ip = ipapi.get()
20 o = ip.options
20 o = ip.options
21 # autocall to "full" mode (smart mode is default, I like full mode)
21 # autocall to "full" mode (smart mode is default, I like full mode)
22
22
23 o.autocall = 2
23 o.autocall = 2
24
24
25 # Jason Orendorff's path class is handy to have in user namespace
25 # Jason Orendorff's path class is handy to have in user namespace
26 # if you are doing shell-like stuff
26 # if you are doing shell-like stuff
27 try:
27 try:
28 ip.ex("from IPython.external.path import path" )
28 ip.ex("from IPython.external.path import path" )
29 except ImportError:
29 except ImportError:
30 pass
30 pass
31
31
32 # beefed up %env is handy in shell mode
32 # beefed up %env is handy in shell mode
33 import envpersist
33 import envpersist
34
34
35 # To see where mycmd resides (in path/aliases), do %which mycmd
35 # To see where mycmd resides (in path/aliases), do %which mycmd
36 import ipy_which
36 import ipy_which
37
37
38 # tab completers for hg, svn, ...
38 # tab completers for hg, svn, ...
39 import ipy_app_completers
39 import ipy_app_completers
40
40
41 # To make executables foo and bar in mybin usable without PATH change, do:
41 # To make executables foo and bar in mybin usable without PATH change, do:
42 # %rehashdir c:/mybin
42 # %rehashdir c:/mybin
43 # %store foo
43 # %store foo
44 # %store bar
44 # %store bar
45 import ipy_rehashdir
45 import ipy_rehashdir
46
46
47 # does not work without subprocess module!
47 # does not work without subprocess module!
48 #import ipy_signals
48 #import ipy_signals
49
49
50 ip.ex('import os')
50 ip.ex('import os')
51 ip.ex("def up(): os.chdir('..')")
51 ip.ex("def up(): os.chdir('..')")
52 ip.user_ns['LA'] = LastArgFinder()
52 ip.user_ns['LA'] = LastArgFinder()
53 # Nice prompt
53 # Nice prompt
54
54
55 o.prompt_in1= r'\C_LightBlue[\C_LightCyan\Y2\C_LightBlue]\C_Green|\#> '
55 o.prompt_in1= r'\C_LightBlue[\C_LightCyan\Y2\C_LightBlue]\C_Green|\#> '
56 o.prompt_in2= r'\C_Green|\C_LightGreen\D\C_Green> '
56 o.prompt_in2= r'\C_Green|\C_LightGreen\D\C_Green> '
57 o.prompt_out= '<\#> '
57 o.prompt_out= '<\#> '
58
58
59 from IPython import Release
59 from IPython import Release
60
60
61 import sys
61 import sys
62 # Non-chatty banner
62 # Non-chatty banner
63 o.banner = "IPython %s [on Py %s]\n" % (Release.version,sys.version.split(None,1)[0])
63 o.banner = "IPython %s [on Py %s]\n" % (Release.version,sys.version.split(None,1)[0])
64
64
65
65
66 ip.IP.default_option('cd','-q')
66 ip.IP.default_option('cd','-q')
67 ip.IP.default_option('macro', '-r')
67 ip.IP.default_option('macro', '-r')
68 # If you only rarely want to execute the things you %edit...
68 # If you only rarely want to execute the things you %edit...
69 #ip.IP.default_option('edit','-x')
69 #ip.IP.default_option('edit','-x')
70
70
71
71
72 o.prompts_pad_left="1"
72 o.prompts_pad_left="1"
73 # Remove all blank lines in between prompts, like a normal shell.
73 # Remove all blank lines in between prompts, like a normal shell.
74 o.separate_in="0"
74 o.separate_in="0"
75 o.separate_out="0"
75 o.separate_out="0"
76 o.separate_out2="0"
76 o.separate_out2="0"
77
77
78 # now alias all syscommands
78 # now alias all syscommands
79
79
80 db = ip.db
80 db = ip.db
81
81
82 syscmds = db.get("syscmdlist",[] )
82 syscmds = db.get("syscmdlist",[] )
83 if not syscmds:
83 if not syscmds:
84 print textwrap.dedent("""
84 print textwrap.dedent("""
85 System command list not initialized, probably the first run...
85 System command list not initialized, probably the first run...
86 running %rehashx to refresh the command list. Run %rehashx
86 running %rehashx to refresh the command list. Run %rehashx
87 again to refresh command list (after installing new software etc.)
87 again to refresh command list (after installing new software etc.)
88 """)
88 """)
89 ip.magic('rehashx')
89 ip.magic('rehashx')
90 syscmds = db.get("syscmdlist")
90 syscmds = db.get("syscmdlist")
91
91
92 # lowcase aliases on win32 only
92 # lowcase aliases on win32 only
93 if os.name == 'posix':
93 if os.name == 'posix':
94 mapper = lambda s:s
94 mapper = lambda s:s
95 else:
95 else:
96 def mapper(s): return s.lower()
96 def mapper(s): return s.lower()
97
97
98 for cmd in syscmds:
98 for cmd in syscmds:
99 # print "sys",cmd #dbg
99 # print "sys",cmd #dbg
100 noext, ext = os.path.splitext(cmd)
100 noext, ext = os.path.splitext(cmd)
101 key = mapper(noext)
101 key = mapper(noext)
102 if key not in ip.IP.alias_table:
102 if key not in ip.IP.alias_table:
103 ip.defalias(key, cmd)
103 ip.defalias(key, cmd)
104
104
105 # mglob combines 'find', recursion, exclusion... '%mglob?' to learn more
105 # mglob combines 'find', recursion, exclusion... '%mglob?' to learn more
106 ip.load("IPython.external.mglob")
106 ip.load("IPython.external.mglob")
107
107
108 # win32 is crippled w/o cygwin, try to help it a little bit
108 # win32 is crippled w/o cygwin, try to help it a little bit
109 if sys.platform == 'win32':
109 if sys.platform == 'win32':
110 if 'cygwin' in os.environ['PATH'].lower():
110 if 'cygwin' in os.environ['PATH'].lower():
111 # use the colors of cygwin ls (recommended)
111 # use the colors of cygwin ls (recommended)
112 ip.defalias('d', 'ls -F --color=auto')
112 ip.defalias('d', 'ls -F --color=auto')
113 else:
113 else:
114 # get icp, imv, imkdir, igrep, irm,...
114 # get icp, imv, imkdir, igrep, irm,...
115 ip.load('ipy_fsops')
115 ip.load('ipy_fsops')
116
116
117 # and the next best thing to real 'ls -F'
117 # and the next best thing to real 'ls -F'
118 ip.defalias('d','dir /w /og /on')
118 ip.defalias('d','dir /w /og /on')
119
119
120 ip.set_hook('input_prefilter', dotslash_prefilter_f)
120 extend_shell_behavior(ip)
121 extend_shell_behavior(ip)
121
122
122 class LastArgFinder:
123 class LastArgFinder:
123 """ Allow $LA to work as "last argument of previous command", like $! in bash
124 """ Allow $LA to work as "last argument of previous command", like $! in bash
124
125
125 To call this in normal IPython code, do LA()
126 To call this in normal IPython code, do LA()
126 """
127 """
127 def __call__(self, hist_idx = None):
128 def __call__(self, hist_idx = None):
128 ip = ipapi.get()
129 ip = ipapi.get()
129 if hist_idx is None:
130 if hist_idx is None:
130 return str(self)
131 return str(self)
131 return ip.IP.input_hist_raw[hist_idx].strip().split()[-1]
132 return ip.IP.input_hist_raw[hist_idx].strip().split()[-1]
132 def __str__(self):
133 def __str__(self):
133 ip = ipapi.get()
134 ip = ipapi.get()
134 for cmd in reversed(ip.IP.input_hist_raw):
135 for cmd in reversed(ip.IP.input_hist_raw):
135 parts = cmd.strip().split()
136 parts = cmd.strip().split()
136 if len(parts) < 2 or parts[-1] in ['$LA', 'LA()']:
137 if len(parts) < 2 or parts[-1] in ['$LA', 'LA()']:
137 continue
138 continue
138 return parts[-1]
139 return parts[-1]
139 return ""
140 return ""
140
141
141
142 def dotslash_prefilter_f(self,line):
142
143 """ ./foo now runs foo as system command
143
144
145 Removes the need for doing !./foo
146 """
147 import IPython.genutils
148 if line.startswith("./"):
149 return "_ip.system(" + IPython.genutils.make_quoted_expr(line)+")"
150 raise ipapi.TryNext
144
151
145 # XXX You do not need to understand the next function!
152 # XXX You do not need to understand the next function!
146 # This should probably be moved out of profile
153 # This should probably be moved out of profile
147
154
148 def extend_shell_behavior(ip):
155 def extend_shell_behavior(ip):
149
156
150 # Instead of making signature a global variable tie it to IPSHELL.
157 # Instead of making signature a global variable tie it to IPSHELL.
151 # In future if it is required to distinguish between different
158 # In future if it is required to distinguish between different
152 # shells we can assign a signature per shell basis
159 # shells we can assign a signature per shell basis
153 ip.IP.__sig__ = 0xa005
160 ip.IP.__sig__ = 0xa005
154 # mark the IPSHELL with this signature
161 # mark the IPSHELL with this signature
155 ip.IP.user_ns['__builtins__'].__dict__['__sig__'] = ip.IP.__sig__
162 ip.IP.user_ns['__builtins__'].__dict__['__sig__'] = ip.IP.__sig__
156
163
157 from IPython.Itpl import ItplNS
164 from IPython.Itpl import ItplNS
158 from IPython.genutils import shell
165 from IPython.genutils import shell
159 # utility to expand user variables via Itpl
166 # utility to expand user variables via Itpl
160 # xxx do something sensible with depth?
167 # xxx do something sensible with depth?
161 ip.IP.var_expand = lambda cmd, lvars=None, depth=2: \
168 ip.IP.var_expand = lambda cmd, lvars=None, depth=2: \
162 str(ItplNS(cmd, ip.IP.user_ns, get_locals()))
169 str(ItplNS(cmd, ip.IP.user_ns, get_locals()))
163
170
164 def get_locals():
171 def get_locals():
165 """ Substituting a variable through Itpl deep inside the IPSHELL stack
172 """ Substituting a variable through Itpl deep inside the IPSHELL stack
166 requires the knowledge of all the variables in scope upto the last
173 requires the knowledge of all the variables in scope upto the last
167 IPSHELL frame. This routine simply merges all the local variables
174 IPSHELL frame. This routine simply merges all the local variables
168 on the IPSHELL stack without worrying about their scope rules
175 on the IPSHELL stack without worrying about their scope rules
169 """
176 """
170 import sys
177 import sys
171 # note lambda expression constitues a function call
178 # note lambda expression constitues a function call
172 # hence fno should be incremented by one
179 # hence fno should be incremented by one
173 getsig = lambda fno: sys._getframe(fno+1).f_globals \
180 getsig = lambda fno: sys._getframe(fno+1).f_globals \
174 ['__builtins__'].__dict__['__sig__']
181 ['__builtins__'].__dict__['__sig__']
175 getlvars = lambda fno: sys._getframe(fno+1).f_locals
182 getlvars = lambda fno: sys._getframe(fno+1).f_locals
176 # trackback until we enter the IPSHELL
183 # trackback until we enter the IPSHELL
177 frame_no = 1
184 frame_no = 1
178 sig = ip.IP.__sig__
185 sig = ip.IP.__sig__
179 fsig = ~sig
186 fsig = ~sig
180 while fsig != sig :
187 while fsig != sig :
181 try:
188 try:
182 fsig = getsig(frame_no)
189 fsig = getsig(frame_no)
183 except (AttributeError, KeyError):
190 except (AttributeError, KeyError):
184 frame_no += 1
191 frame_no += 1
185 except ValueError:
192 except ValueError:
186 # stack is depleted
193 # stack is depleted
187 # call did not originate from IPSHELL
194 # call did not originate from IPSHELL
188 return {}
195 return {}
189 first_frame = frame_no
196 first_frame = frame_no
190 # walk further back until we exit from IPSHELL or deplete stack
197 # walk further back until we exit from IPSHELL or deplete stack
191 try:
198 try:
192 while(sig == getsig(frame_no+1)):
199 while(sig == getsig(frame_no+1)):
193 frame_no += 1
200 frame_no += 1
194 except (AttributeError, KeyError, ValueError):
201 except (AttributeError, KeyError, ValueError):
195 pass
202 pass
196 # merge the locals from top down hence overriding
203 # merge the locals from top down hence overriding
197 # any re-definitions of variables, functions etc.
204 # any re-definitions of variables, functions etc.
198 lvars = {}
205 lvars = {}
199 for fno in range(frame_no, first_frame-1, -1):
206 for fno in range(frame_no, first_frame-1, -1):
200 lvars.update(getlvars(fno))
207 lvars.update(getlvars(fno))
201 #print '\n'*5, first_frame, frame_no, '\n', lvars, '\n'*5 #dbg
208 #print '\n'*5, first_frame, frame_no, '\n', lvars, '\n'*5 #dbg
202 return lvars
209 return lvars
203
210
204 def _runlines(lines):
211 def _runlines(lines):
205 """Run a string of one or more lines of source.
212 """Run a string of one or more lines of source.
206
213
207 This method is capable of running a string containing multiple source
214 This method is capable of running a string containing multiple source
208 lines, as if they had been entered at the IPython prompt. Since it
215 lines, as if they had been entered at the IPython prompt. Since it
209 exposes IPython's processing machinery, the given strings can contain
216 exposes IPython's processing machinery, the given strings can contain
210 magic calls (%magic), special shell access (!cmd), etc."""
217 magic calls (%magic), special shell access (!cmd), etc."""
211
218
212 # We must start with a clean buffer, in case this is run from an
219 # We must start with a clean buffer, in case this is run from an
213 # interactive IPython session (via a magic, for example).
220 # interactive IPython session (via a magic, for example).
214 ip.IP.resetbuffer()
221 ip.IP.resetbuffer()
215 lines = lines.split('\n')
222 lines = lines.split('\n')
216 more = 0
223 more = 0
217 command = ''
224 command = ''
218 for line in lines:
225 for line in lines:
219 # skip blank lines so we don't mess up the prompt counter, but do
226 # skip blank lines so we don't mess up the prompt counter, but do
220 # NOT skip even a blank line if we are in a code block (more is
227 # NOT skip even a blank line if we are in a code block (more is
221 # true)
228 # true)
222 # if command is not empty trim the line
229 # if command is not empty trim the line
223 if command != '' :
230 if command != '' :
224 line = line.strip()
231 line = line.strip()
225 # add the broken line to the command
232 # add the broken line to the command
226 if line and line[-1] == '\\' :
233 if line and line[-1] == '\\' :
227 command += line[0:-1] + ' '
234 command += line[0:-1] + ' '
228 more = True
235 more = True
229 continue
236 continue
230 else :
237 else :
231 # add the last (current) line to the command
238 # add the last (current) line to the command
232 command += line
239 command += line
233 if command or more:
240 if command or more:
234 # push to raw history, so hist line numbers stay in sync
241 # push to raw history, so hist line numbers stay in sync
235 ip.IP.input_hist_raw.append("# " + command + "\n")
242 ip.IP.input_hist_raw.append("# " + command + "\n")
236
243
237 more = ip.IP.push(ip.IP.prefilter(command,more))
244 more = ip.IP.push(ip.IP.prefilter(command,more))
238 command = ''
245 command = ''
239 # IPython's runsource returns None if there was an error
246 # IPython's runsource returns None if there was an error
240 # compiling the code. This allows us to stop processing right
247 # compiling the code. This allows us to stop processing right
241 # away, so the user gets the error message at the right place.
248 # away, so the user gets the error message at the right place.
242 if more is None:
249 if more is None:
243 break
250 break
244 # final newline in case the input didn't have it, so that the code
251 # final newline in case the input didn't have it, so that the code
245 # actually does get executed
252 # actually does get executed
246 if more:
253 if more:
247 ip.IP.push('\n')
254 ip.IP.push('\n')
248
255
249 ip.IP.runlines = _runlines
256 ip.IP.runlines = _runlines
250
257
251 main()
258 main()
@@ -1,107 +1,116 b''
1 """ User configuration file for IPython
1 """ User configuration file for IPython
2
2
3 This is a more flexible and safe way to configure ipython than *rc files
3 This is a more flexible and safe way to configure ipython than *rc files
4 (ipythonrc, ipythonrc-pysh etc.)
4 (ipythonrc, ipythonrc-pysh etc.)
5
5
6 This file is always imported on ipython startup. You can import the
6 This file is always imported on ipython startup. You can import the
7 ipython extensions you need here (see IPython/Extensions directory).
7 ipython extensions you need here (see IPython/Extensions directory).
8
8
9 Feel free to edit this file to customize your ipython experience.
9 Feel free to edit this file to customize your ipython experience.
10
10
11 Note that as such this file does nothing, for backwards compatibility.
11 Note that as such this file does nothing, for backwards compatibility.
12 Consult e.g. file 'ipy_profile_sh.py' for an example of the things
12 Consult e.g. file 'ipy_profile_sh.py' for an example of the things
13 you can do here.
13 you can do here.
14
14
15 See http://ipython.scipy.org/moin/IpythonExtensionApi for detailed
15 See http://ipython.scipy.org/moin/IpythonExtensionApi for detailed
16 description on what you could do here.
16 description on what you could do here.
17 """
17 """
18
18
19 # Most of your config files and extensions will probably start with this import
19 # Most of your config files and extensions will probably start with this import
20
20
21 import IPython.ipapi
21 import IPython.ipapi
22 ip = IPython.ipapi.get()
22 ip = IPython.ipapi.get()
23
23
24 # You probably want to uncomment this if you did %upgrade -nolegacy
24 # You probably want to uncomment this if you did %upgrade -nolegacy
25 # import ipy_defaults
25 # import ipy_defaults
26
26
27 import os
27 import os
28
28
29 def main():
29 def main():
30
30
31 # uncomment if you want to get ipython -p sh behaviour
31 # uncomment if you want to get ipython -p sh behaviour
32 # without having to use command line switches
32 # without having to use command line switches
33 # import ipy_profile_sh
33 # import ipy_profile_sh
34
34
35 # Configure your favourite editor?
35 # Configure your favourite editor?
36 # Good idea e.g. for %edit os.path.isfile
36 # Good idea e.g. for %edit os.path.isfile
37
37
38 #import ipy_editors
38 #import ipy_editors
39
39
40 # Choose one of these:
40 # Choose one of these:
41
41
42 #ipy_editors.scite()
42 #ipy_editors.scite()
43 #ipy_editors.scite('c:/opt/scite/scite.exe')
43 #ipy_editors.scite('c:/opt/scite/scite.exe')
44 #ipy_editors.komodo()
44 #ipy_editors.komodo()
45 #ipy_editors.idle()
45 #ipy_editors.idle()
46 # ... or many others, try 'ipy_editors??' after import to see them
46 # ... or many others, try 'ipy_editors??' after import to see them
47
47
48 # Or roll your own:
48 # Or roll your own:
49 #ipy_editors.install_editor("c:/opt/jed +$line $file")
49 #ipy_editors.install_editor("c:/opt/jed +$line $file")
50
50
51
51
52 o = ip.options
52 o = ip.options
53 # An example on how to set options
53 # An example on how to set options
54 #o.autocall = 1
54 #o.autocall = 1
55 o.system_verbose = 0
55 o.system_verbose = 0
56
56
57 #import_all("os sys")
57 #import_all("os sys")
58 #execf('~/_ipython/ns.py')
58 #execf('~/_ipython/ns.py')
59
59
60
60
61 # -- prompt
61 # -- prompt
62 # A different, more compact set of prompts from the default ones, that
62 # A different, more compact set of prompts from the default ones, that
63 # always show your current location in the filesystem:
63 # always show your current location in the filesystem:
64
64
65 #o.prompt_in1 = r'\C_LightBlue[\C_LightCyan\Y2\C_LightBlue]\C_Normal\n\C_Green|\#>'
65 #o.prompt_in1 = r'\C_LightBlue[\C_LightCyan\Y2\C_LightBlue]\C_Normal\n\C_Green|\#>'
66 #o.prompt_in2 = r'.\D: '
66 #o.prompt_in2 = r'.\D: '
67 #o.prompt_out = r'[\#] '
67 #o.prompt_out = r'[\#] '
68
68
69 # Try one of these color settings if you can't read the text easily
69 # Try one of these color settings if you can't read the text easily
70 # autoexec is a list of IPython commands to execute on startup
70 # autoexec is a list of IPython commands to execute on startup
71 #o.autoexec.append('%colors LightBG')
71 #o.autoexec.append('%colors LightBG')
72 #o.autoexec.append('%colors NoColor')
72 #o.autoexec.append('%colors NoColor')
73 #o.autoexec.append('%colors Linux')
73 #o.autoexec.append('%colors Linux')
74
74
75 # for sane integer division that converts to float (1/2 == 0.5)
75 # for sane integer division that converts to float (1/2 == 0.5)
76 #o.autoexec.append('from __future__ import division')
76 #o.autoexec.append('from __future__ import division')
77
77
78 # For %tasks and %kill
78 # For %tasks and %kill
79 #import jobctrl
79 #import jobctrl
80
80
81 # For autoreloading of modules (%autoreload, %aimport)
81 # For autoreloading of modules (%autoreload, %aimport)
82 #import ipy_autoreload
82 #import ipy_autoreload
83
83
84 # For winpdb support (%wdb)
84 # For winpdb support (%wdb)
85 #import ipy_winpdb
85 #import ipy_winpdb
86
86
87 # For bzr completer, requires bzrlib (the python installation of bzr)
87 # For bzr completer, requires bzrlib (the python installation of bzr)
88 #ip.load('ipy_bzr')
88 #ip.load('ipy_bzr')
89
89
90 # Tab completer that is not quite so picky (i.e.
90 # Tab completer that is not quite so picky (i.e.
91 # "foo".<TAB> and str(2).<TAB> will work). Complete
91 # "foo".<TAB> and str(2).<TAB> will work). Complete
92 # at your own risk!
92 # at your own risk!
93 #import ipy_greedycompleter
93 #import ipy_greedycompleter
94
94
95 # If you are on Linux, you may be annoyed by
96 # "Display all N possibilities? (y or n)" on tab completion,
97 # as well as the paging through "more". Uncomment the following
98 # lines to disable that behaviour
99 #import readline
100 #readline.parse_and_bind('set completion-query-items 1000')
101 #readline.parse_and_bind('set page-completions no')
102
103
95
104
96
105
97 # some config helper functions you can use
106 # some config helper functions you can use
98 def import_all(modules):
107 def import_all(modules):
99 """ Usage: import_all("os sys") """
108 """ Usage: import_all("os sys") """
100 for m in modules.split():
109 for m in modules.split():
101 ip.ex("from %s import *" % m)
110 ip.ex("from %s import *" % m)
102
111
103 def execf(fname):
112 def execf(fname):
104 """ Execute a file in user namespace """
113 """ Execute a file in user namespace """
105 ip.ex('execfile("%s")' % os.path.expanduser(fname))
114 ip.ex('execfile("%s")' % os.path.expanduser(fname))
106
115
107 main()
116 main()
@@ -1,395 +1,425 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
3
3
4 """PyObjC classes to provide a Cocoa frontend to the ipython1.kernel.engineservice.EngineService.
4 """PyObjC classes to provide a Cocoa frontend to the
5 IPython.kernel.engineservice.IEngineBase.
5
6
6 The Cocoa frontend is divided into two classes:
7 To add an IPython interpreter to a cocoa app, instantiate an
7 - IPythonCocoaController
8 IPythonCocoaController in a XIB and connect its textView outlet to an
8 - IPythonCLITextViewDelegate
9 NSTextView instance in your UI. That's it.
9
10
10 To add an IPython interpreter to a cocoa app, instantiate both of these classes in an XIB...[FINISH]
11 Author: Barry Wark
11 """
12 """
12
13
13 __docformat__ = "restructuredtext en"
14 __docformat__ = "restructuredtext en"
14
15
15 #-------------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008 Barry Wark <barrywark@gmail.com>
17 # Copyright (C) 2008 The IPython Development Team
17 #
18 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
21
22
22 #-------------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
23 # Imports
24 # Imports
24 #-------------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
25
26
26 import objc
27 import objc
27 import uuid
28 import uuid
28
29
29 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
30 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
30 NSLog, NSNotificationCenter, NSMakeRange,\
31 NSLog, NSNotificationCenter, NSMakeRange,\
31 NSLocalizedString, NSIntersectionRange
32 NSLocalizedString, NSIntersectionRange
32
33
33 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
34 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
34 NSTextView, NSRulerView, NSVerticalRuler
35 NSTextView, NSRulerView, NSVerticalRuler
35
36
36 from pprint import saferepr
37 from pprint import saferepr
37
38
38 import IPython
39 import IPython
39 from IPython.kernel.engineservice import EngineService, ThreadedEngineService
40 from IPython.kernel.engineservice import ThreadedEngineService
40 from IPython.frontend.frontendbase import FrontEndBase
41 from IPython.frontend.frontendbase import FrontEndBase
41
42
42 from twisted.internet.threads import blockingCallFromThread
43 from twisted.internet.threads import blockingCallFromThread
43 from twisted.python.failure import Failure
44 from twisted.python.failure import Failure
44
45
45 #-------------------------------------------------------------------------------
46 #------------------------------------------------------------------------------
46 # Classes to implement the Cocoa frontend
47 # Classes to implement the Cocoa frontend
47 #-------------------------------------------------------------------------------
48 #------------------------------------------------------------------------------
48
49
49 # TODO:
50 # TODO:
50 # 1. use MultiEngineClient and out-of-process engine rather than ThreadedEngineService?
51 # 1. use MultiEngineClient and out-of-process engine rather than
52 # ThreadedEngineService?
51 # 2. integrate Xgrid launching of engines
53 # 2. integrate Xgrid launching of engines
52
54
53
55
54
56
55
57
56 class IPythonCocoaController(NSObject, FrontEndBase):
58 class IPythonCocoaController(NSObject, FrontEndBase):
57 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
59 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
58 waitingForEngine = objc.ivar().bool()
60 waitingForEngine = objc.ivar().bool()
59 textView = objc.IBOutlet()
61 textView = objc.IBOutlet()
60
62
61 def init(self):
63 def init(self):
62 self = super(IPythonCocoaController, self).init()
64 self = super(IPythonCocoaController, self).init()
63 FrontEndBase.__init__(self, engine=ThreadedEngineService())
65 FrontEndBase.__init__(self, engine=ThreadedEngineService())
64 if(self != None):
66 if(self != None):
65 self._common_init()
67 self._common_init()
66
68
67 return self
69 return self
68
70
69 def _common_init(self):
71 def _common_init(self):
70 """_common_init"""
72 """_common_init"""
71
73
72 self.userNS = NSMutableDictionary.dictionary()
74 self.userNS = NSMutableDictionary.dictionary()
73 self.waitingForEngine = False
75 self.waitingForEngine = False
74
76
75 self.lines = {}
77 self.lines = {}
76 self.tabSpaces = 4
78 self.tabSpaces = 4
77 self.tabUsesSpaces = True
79 self.tabUsesSpaces = True
78 self.currentBlockID = self.nextBlockID()
80 self.currentBlockID = self.next_block_ID()
79 self.blockRanges = {} # blockID=>NSRange
81 self.blockRanges = {} # blockID=>NSRange
80
82
81
83
82 def awakeFromNib(self):
84 def awakeFromNib(self):
83 """awakeFromNib"""
85 """awakeFromNib"""
84
86
85 self._common_init()
87 self._common_init()
86
88
87 # Start the IPython engine
89 # Start the IPython engine
88 self.engine.startService()
90 self.engine.startService()
89 NSLog('IPython engine started')
91 NSLog('IPython engine started')
90
92
91 # Register for app termination
93 # Register for app termination
92 NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(self,
94 nc = NSNotificationCenter.defaultCenter()
93 'appWillTerminate:',
95 nc.addObserver_selector_name_object_(
94 NSApplicationWillTerminateNotification,
96 self,
95 None)
97 'appWillTerminate:',
98 NSApplicationWillTerminateNotification,
99 None)
96
100
97 self.textView.setDelegate_(self)
101 self.textView.setDelegate_(self)
98 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
102 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
99 self.verticalRulerView = NSRulerView.alloc().initWithScrollView_orientation_(
103 r = NSRulerView.alloc().initWithScrollView_orientation_(
100 self.textView.enclosingScrollView(),
104 self.textView.enclosingScrollView(),
101 NSVerticalRuler)
105 NSVerticalRuler)
106 self.verticalRulerView = r
102 self.verticalRulerView.setClientView_(self.textView)
107 self.verticalRulerView.setClientView_(self.textView)
103 self.startCLIForTextView()
108 self._start_cli_banner()
104
109
105
110
106 def appWillTerminate_(self, notification):
111 def appWillTerminate_(self, notification):
107 """appWillTerminate"""
112 """appWillTerminate"""
108
113
109 self.engine.stopService()
114 self.engine.stopService()
110
115
111
116
112 def complete(self, token):
117 def complete(self, token):
113 """Complete token in engine's user_ns
118 """Complete token in engine's user_ns
114
119
115 Parameters
120 Parameters
116 ----------
121 ----------
117 token : string
122 token : string
118
123
119 Result
124 Result
120 ------
125 ------
121 Deferred result of ipython1.kernel.engineservice.IEngineInteractive.complete
126 Deferred result of
127 IPython.kernel.engineservice.IEngineBase.complete
122 """
128 """
123
129
124 return self.engine.complete(token)
130 return self.engine.complete(token)
125
131
126
132
127 def execute(self, block, blockID=None):
133 def execute(self, block, blockID=None):
128 self.waitingForEngine = True
134 self.waitingForEngine = True
129 self.willChangeValueForKey_('commandHistory')
135 self.willChangeValueForKey_('commandHistory')
130 d = super(IPythonCocoaController, self).execute(block, blockID)
136 d = super(IPythonCocoaController, self).execute(block, blockID)
131 d.addBoth(self._engineDone)
137 d.addBoth(self._engine_done)
132 d.addCallback(self._updateUserNS)
138 d.addCallback(self._update_user_ns)
133
139
134 return d
140 return d
135
141
136
142
137 def _engineDone(self, x):
143 def _engine_done(self, x):
138 self.waitingForEngine = False
144 self.waitingForEngine = False
139 self.didChangeValueForKey_('commandHistory')
145 self.didChangeValueForKey_('commandHistory')
140 return x
146 return x
141
147
142 def _updateUserNS(self, result):
148 def _update_user_ns(self, result):
143 """Update self.userNS from self.engine's namespace"""
149 """Update self.userNS from self.engine's namespace"""
144 d = self.engine.keys()
150 d = self.engine.keys()
145 d.addCallback(self._getEngineNamepsaceValuesForKeys)
151 d.addCallback(self._get_engine_namespace_values_for_keys)
146
152
147 return result
153 return result
148
154
149
155
150 def _getEngineNamepsaceValuesForKeys(self, keys):
156 def _get_engine_namespace_values_for_keys(self, keys):
151 d = self.engine.pull(keys)
157 d = self.engine.pull(keys)
152 d.addCallback(self._storeEngineNamespaceValues, keys=keys)
158 d.addCallback(self._store_engine_namespace_values, keys=keys)
153
159
154
160
155 def _storeEngineNamespaceValues(self, values, keys=[]):
161 def _store_engine_namespace_values(self, values, keys=[]):
156 assert(len(values) == len(keys))
162 assert(len(values) == len(keys))
157 self.willChangeValueForKey_('userNS')
163 self.willChangeValueForKey_('userNS')
158 for (k,v) in zip(keys,values):
164 for (k,v) in zip(keys,values):
159 self.userNS[k] = saferepr(v)
165 self.userNS[k] = saferepr(v)
160 self.didChangeValueForKey_('userNS')
166 self.didChangeValueForKey_('userNS')
161
167
162
168
163 def startCLIForTextView(self):
169 def update_cell_prompt(self, result):
170 if(isinstance(result, Failure)):
171 blockID = result.blockID
172 else:
173 blockID = result['blockID']
174
175
176 self.insert_text(self.input_prompt(result=result),
177 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
178 scrollToVisible=False
179 )
180
181 return result
182
183
184 def render_result(self, result):
185 blockID = result['blockID']
186 inputRange = self.blockRanges[blockID]
187 del self.blockRanges[blockID]
188
189 #print inputRange,self.current_block_range()
190 self.insert_text('\n' +
191 self.output_prompt(result) +
192 result.get('display',{}).get('pprint','') +
193 '\n\n',
194 textRange=NSMakeRange(inputRange.location+inputRange.length,
195 0))
196 return result
197
198
199 def render_error(self, failure):
200 self.insert_text('\n\n'+str(failure)+'\n\n')
201 self.start_new_block()
202 return failure
203
204
205 def _start_cli_banner(self):
164 """Print banner"""
206 """Print banner"""
165
207
166 banner = """IPython1 %s -- An enhanced Interactive Python.""" % IPython.__version__
208 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
209 IPython.__version__
167
210
168 self.insert_text(banner + '\n\n')
211 self.insert_text(banner + '\n\n')
169
212
170 # NSTextView/IPythonTextView delegate methods
213
214 def start_new_block(self):
215 """"""
216
217 self.currentBlockID = self.next_block_ID()
218
219
220
221 def next_block_ID(self):
222
223 return uuid.uuid4()
224
225 def current_block_range(self):
226 return self.blockRanges.get(self.currentBlockID,
227 NSMakeRange(self.textView.textStorage().length(),
228 0))
229
230 def current_block(self):
231 """The current block's text"""
232
233 return self.text_for_range(self.current_block_range())
234
235 def text_for_range(self, textRange):
236 """text_for_range"""
237
238 ts = self.textView.textStorage()
239 return ts.string().substringWithRange_(textRange)
240
241 def current_line(self):
242 block = self.text_for_range(self.current_block_range())
243 block = block.split('\n')
244 return block[-1]
245
246
247 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
248 """Insert text into textView at textRange, updating blockRanges
249 as necessary
250 """
251
252 if(textRange == None):
253 #range for end of text
254 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
255
256 for r in self.blockRanges.itervalues():
257 intersection = NSIntersectionRange(r,textRange)
258 if(intersection.length == 0): #ranges don't intersect
259 if r.location >= textRange.location:
260 r.location += len(string)
261 else: #ranges intersect
262 if(r.location <= textRange.location):
263 assert(intersection.length == textRange.length)
264 r.length += textRange.length
265 else:
266 r.location += intersection.length
267
268 self.textView.replaceCharactersInRange_withString_(
269 textRange, string)
270 self.textView.setSelectedRange_(
271 NSMakeRange(textRange.location+len(string), 0))
272 if(scrollToVisible):
273 self.textView.scrollRangeToVisible_(textRange)
274
275
276
277
278 def replace_current_block_with_string(self, textView, string):
279 textView.replaceCharactersInRange_withString_(
280 self.current_block_range(),
281 string)
282 self.current_block_range().length = len(string)
283 r = NSMakeRange(textView.textStorage().length(), 0)
284 textView.scrollRangeToVisible_(r)
285 textView.setSelectedRange_(r)
286
287
288 def current_indent_string(self):
289 """returns string for indent or None if no indent"""
290
291 if(len(self.current_block()) > 0):
292 lines = self.current_block().split('\n')
293 currentIndent = len(lines[-1]) - len(lines[-1])
294 if(currentIndent == 0):
295 currentIndent = self.tabSpaces
296
297 if(self.tabUsesSpaces):
298 result = ' ' * currentIndent
299 else:
300 result = '\t' * (currentIndent/self.tabSpaces)
301 else:
302 result = None
303
304 return result
305
306
307 # NSTextView delegate methods...
171 def textView_doCommandBySelector_(self, textView, selector):
308 def textView_doCommandBySelector_(self, textView, selector):
172 assert(textView == self.textView)
309 assert(textView == self.textView)
173 NSLog("textView_doCommandBySelector_: "+selector)
310 NSLog("textView_doCommandBySelector_: "+selector)
174
311
175
312
176 if(selector == 'insertNewline:'):
313 if(selector == 'insertNewline:'):
177 indent = self.currentIndentString()
314 indent = self.current_indent_string()
178 if(indent):
315 if(indent):
179 line = indent + self.currentLine()
316 line = indent + self.current_line()
180 else:
317 else:
181 line = self.currentLine()
318 line = self.current_line()
182
319
183 if(self.is_complete(self.currentBlock())):
320 if(self.is_complete(self.current_block())):
184 self.execute(self.currentBlock(),
321 self.execute(self.current_block(),
185 blockID=self.currentBlockID)
322 blockID=self.currentBlockID)
186 self.startNewBlock()
323 self.start_new_block()
187
324
188 return True
325 return True
189
326
190 return False
327 return False
191
328
192 elif(selector == 'moveUp:'):
329 elif(selector == 'moveUp:'):
193 prevBlock = self.get_history_previous(self.currentBlock())
330 prevBlock = self.get_history_previous(self.current_block())
194 if(prevBlock != None):
331 if(prevBlock != None):
195 self.replaceCurrentBlockWithString(textView, prevBlock)
332 self.replace_current_block_with_string(textView, prevBlock)
196 else:
333 else:
197 NSBeep()
334 NSBeep()
198 return True
335 return True
199
336
200 elif(selector == 'moveDown:'):
337 elif(selector == 'moveDown:'):
201 nextBlock = self.get_history_next()
338 nextBlock = self.get_history_next()
202 if(nextBlock != None):
339 if(nextBlock != None):
203 self.replaceCurrentBlockWithString(textView, nextBlock)
340 self.replace_current_block_with_string(textView, nextBlock)
204 else:
341 else:
205 NSBeep()
342 NSBeep()
206 return True
343 return True
207
344
208 elif(selector == 'moveToBeginningOfParagraph:'):
345 elif(selector == 'moveToBeginningOfParagraph:'):
209 textView.setSelectedRange_(NSMakeRange(self.currentBlockRange().location, 0))
346 textView.setSelectedRange_(NSMakeRange(
347 self.current_block_range().location,
348 0))
210 return True
349 return True
211 elif(selector == 'moveToEndOfParagraph:'):
350 elif(selector == 'moveToEndOfParagraph:'):
212 textView.setSelectedRange_(NSMakeRange(self.currentBlockRange().location + \
351 textView.setSelectedRange_(NSMakeRange(
213 self.currentBlockRange().length, 0))
352 self.current_block_range().location + \
353 self.current_block_range().length, 0))
214 return True
354 return True
215 elif(selector == 'deleteToEndOfParagraph:'):
355 elif(selector == 'deleteToEndOfParagraph:'):
216 if(textView.selectedRange().location <= self.currentBlockRange().location):
356 if(textView.selectedRange().location <= \
357 self.current_block_range().location):
217 # Intersect the selected range with the current line range
358 # Intersect the selected range with the current line range
218 if(self.currentBlockRange().length < 0):
359 if(self.current_block_range().length < 0):
219 self.blockRanges[self.currentBlockID].length = 0
360 self.blockRanges[self.currentBlockID].length = 0
220
361
221 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
362 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
222 self.currentBlockRange())
363 self.current_block_range())
223
364
224 if(r.length > 0): #no intersection
365 if(r.length > 0): #no intersection
225 textView.setSelectedRange_(r)
366 textView.setSelectedRange_(r)
226
367
227 return False # don't actually handle the delete
368 return False # don't actually handle the delete
228
369
229 elif(selector == 'insertTab:'):
370 elif(selector == 'insertTab:'):
230 if(len(self.currentLine().strip()) == 0): #only white space
371 if(len(self.current_line().strip()) == 0): #only white space
231 return False
372 return False
232 else:
373 else:
233 self.textView.complete_(self)
374 self.textView.complete_(self)
234 return True
375 return True
235
376
236 elif(selector == 'deleteBackward:'):
377 elif(selector == 'deleteBackward:'):
237 #if we're at the beginning of the current block, ignore
378 #if we're at the beginning of the current block, ignore
238 if(textView.selectedRange().location == self.currentBlockRange().location):
379 if(textView.selectedRange().location == \
380 self.current_block_range().location):
239 return True
381 return True
240 else:
382 else:
241 self.currentBlockRange().length-=1
383 self.current_block_range().length-=1
242 return False
384 return False
243 return False
385 return False
244
386
245
387
246 def textView_shouldChangeTextInRanges_replacementStrings_(self, textView, ranges, replacementStrings):
388 def textView_shouldChangeTextInRanges_replacementStrings_(self,
389 textView, ranges, replacementStrings):
247 """
390 """
248 Delegate method for NSTextView.
391 Delegate method for NSTextView.
249
392
250 Refuse change text in ranges not at end, but make those changes at end.
393 Refuse change text in ranges not at end, but make those changes at
394 end.
251 """
395 """
252
396
253 #print 'textView_shouldChangeTextInRanges_replacementStrings_:',ranges,replacementStrings
254 assert(len(ranges) == len(replacementStrings))
397 assert(len(ranges) == len(replacementStrings))
255 allow = True
398 allow = True
256 for r,s in zip(ranges, replacementStrings):
399 for r,s in zip(ranges, replacementStrings):
257 r = r.rangeValue()
400 r = r.rangeValue()
258 if(textView.textStorage().length() > 0 and
401 if(textView.textStorage().length() > 0 and
259 r.location < self.currentBlockRange().location):
402 r.location < self.current_block_range().location):
260 self.insert_text(s)
403 self.insert_text(s)
261 allow = False
404 allow = False
262
405
263
406
264 self.blockRanges.setdefault(self.currentBlockID, self.currentBlockRange()).length += len(s)
407 self.blockRanges.setdefault(self.currentBlockID,
408 self.current_block_range()).length +=\
409 len(s)
265
410
266 return allow
411 return allow
267
412
268 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, textView, words, charRange, index):
413 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
414 textView, words, charRange, index):
269 try:
415 try:
270 token = textView.textStorage().string().substringWithRange_(charRange)
416 ts = textView.textStorage()
417 token = ts.string().substringWithRange_(charRange)
271 completions = blockingCallFromThread(self.complete, token)
418 completions = blockingCallFromThread(self.complete, token)
272 except:
419 except:
273 completions = objc.nil
420 completions = objc.nil
274 NSBeep()
421 NSBeep()
275
422
276 return (completions,0)
423 return (completions,0)
277
424
278
279 def startNewBlock(self):
280 """"""
281
282 self.currentBlockID = self.nextBlockID()
283
284
285
286 def nextBlockID(self):
287
288 return uuid.uuid4()
289
290 def currentBlockRange(self):
291 return self.blockRanges.get(self.currentBlockID, NSMakeRange(self.textView.textStorage().length(), 0))
292
293 def currentBlock(self):
294 """The current block's text"""
295
296 return self.textForRange(self.currentBlockRange())
297
298 def textForRange(self, textRange):
299 """textForRange"""
300
301 return self.textView.textStorage().string().substringWithRange_(textRange)
302
303 def currentLine(self):
304 block = self.textForRange(self.currentBlockRange())
305 block = block.split('\n')
306 return block[-1]
307
308 def update_cell_prompt(self, result):
309 if(isinstance(result, Failure)):
310 blockID = result.blockID
311 else:
312 blockID = result['blockID']
313
314
315 self.insert_text(self.input_prompt(result=result),
316 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
317 scrollToVisible=False
318 )
319
320 return result
321
322
323 def render_result(self, result):
324 blockID = result['blockID']
325 inputRange = self.blockRanges[blockID]
326 del self.blockRanges[blockID]
327
328 #print inputRange,self.currentBlockRange()
329 self.insert_text('\n' +
330 self.output_prompt(result) +
331 result.get('display',{}).get('pprint','') +
332 '\n\n',
333 textRange=NSMakeRange(inputRange.location+inputRange.length, 0))
334 return result
335
336
337 def render_error(self, failure):
338 self.insert_text('\n\n'+str(failure)+'\n\n')
339 self.startNewBlock()
340 return failure
341
342
343 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
344 """Insert text into textView at textRange, updating blockRanges as necessary"""
345
346 if(textRange == None):
347 textRange = NSMakeRange(self.textView.textStorage().length(), 0) #range for end of text
348
349 for r in self.blockRanges.itervalues():
350 intersection = NSIntersectionRange(r,textRange)
351 if(intersection.length == 0): #ranges don't intersect
352 if r.location >= textRange.location:
353 r.location += len(string)
354 else: #ranges intersect
355 if(r.location <= textRange.location):
356 assert(intersection.length == textRange.length)
357 r.length += textRange.length
358 else:
359 r.location += intersection.length
360
361 self.textView.replaceCharactersInRange_withString_(textRange, string) #textStorage().string()
362 self.textView.setSelectedRange_(NSMakeRange(textRange.location+len(string), 0))
363 if(scrollToVisible):
364 self.textView.scrollRangeToVisible_(textRange)
365
366
367
368 def replaceCurrentBlockWithString(self, textView, string):
369 textView.replaceCharactersInRange_withString_(self.currentBlockRange(),
370 string)
371 self.currentBlockRange().length = len(string)
372 r = NSMakeRange(textView.textStorage().length(), 0)
373 textView.scrollRangeToVisible_(r)
374 textView.setSelectedRange_(r)
375
376
377 def currentIndentString(self):
378 """returns string for indent or None if no indent"""
379
380 if(len(self.currentBlock()) > 0):
381 lines = self.currentBlock().split('\n')
382 currentIndent = len(lines[-1]) - len(lines[-1])
383 if(currentIndent == 0):
384 currentIndent = self.tabSpaces
385
386 if(self.tabUsesSpaces):
387 result = ' ' * currentIndent
388 else:
389 result = '\t' * (currentIndent/self.tabSpaces)
390 else:
391 result = None
392
393 return result
394
395
425
@@ -1,77 +1,72 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """This file contains unittests for the ipython1.frontend.cocoa.cocoa_frontend module.
2 """This file contains unittests for the
3
3 IPython.frontend.cocoa.cocoa_frontend module.
4 Things that should be tested:
5
6 - IPythonCocoaController instantiates an IEngineInteractive
7 - IPythonCocoaController executes code on the engine
8 - IPythonCocoaController mirrors engine's user_ns
9 """
4 """
10 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
11
6
12 #-------------------------------------------------------------------------------
7 #---------------------------------------------------------------------------
13 # Copyright (C) 2005 Fernando Perez <fperez@colorado.edu>
8 # Copyright (C) 2005 The IPython Development Team
14 # Brian E Granger <ellisonbg@gmail.com>
9 #
15 # Benjamin Ragan-Kelley <benjaminrk@gmail.com>
10 # Distributed under the terms of the BSD License. The full license is in
16 #
11 # the file COPYING, distributed as part of this software.
17 # Distributed under the terms of the BSD License. The full license is in
12 #---------------------------------------------------------------------------
18 # the file COPYING, distributed as part of this software.
13
19 #-------------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
20
15 # Imports
21 #-------------------------------------------------------------------------------
16 #---------------------------------------------------------------------------
22 # Imports
17 from IPython.kernel.core.interpreter import Interpreter
23 #-------------------------------------------------------------------------------
24 from IPython.kernel.core.interpreter import Interpreter
25 import IPython.kernel.engineservice as es
18 import IPython.kernel.engineservice as es
26 from IPython.testing.util import DeferredTestCase
19 from IPython.testing.util import DeferredTestCase
27 from twisted.internet.defer import succeed
20 from twisted.internet.defer import succeed
28 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
21 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
29
22
30 from Foundation import NSMakeRect
23 from Foundation import NSMakeRect
31 from AppKit import NSTextView, NSScrollView
24 from AppKit import NSTextView, NSScrollView
32
25
33 class TestIPythonCocoaControler(DeferredTestCase):
26 class TestIPythonCocoaControler(DeferredTestCase):
34 """Tests for IPythonCocoaController"""
27 """Tests for IPythonCocoaController"""
35
28
36 def setUp(self):
29 def setUp(self):
37 self.controller = IPythonCocoaController.alloc().init()
30 self.controller = IPythonCocoaController.alloc().init()
38 self.engine = es.EngineService()
31 self.engine = es.EngineService()
39 self.engine.startService()
32 self.engine.startService()
40
33
41
34
42 def tearDown(self):
35 def tearDown(self):
43 self.controller = None
36 self.controller = None
44 self.engine.stopService()
37 self.engine.stopService()
45
38
46 def testControllerExecutesCode(self):
39 def testControllerExecutesCode(self):
47 code ="""5+5"""
40 code ="""5+5"""
48 expected = Interpreter().execute(code)
41 expected = Interpreter().execute(code)
49 del expected['number']
42 del expected['number']
50 def removeNumberAndID(result):
43 def removeNumberAndID(result):
51 del result['number']
44 del result['number']
52 del result['id']
45 del result['id']
53 return result
46 return result
54 self.assertDeferredEquals(self.controller.execute(code).addCallback(removeNumberAndID), expected)
47 self.assertDeferredEquals(
48 self.controller.execute(code).addCallback(removeNumberAndID),
49 expected)
55
50
56 def testControllerMirrorsUserNSWithValuesAsStrings(self):
51 def testControllerMirrorsUserNSWithValuesAsStrings(self):
57 code = """userns1=1;userns2=2"""
52 code = """userns1=1;userns2=2"""
58 def testControllerUserNS(result):
53 def testControllerUserNS(result):
59 self.assertEquals(self.controller.userNS['userns1'], 1)
54 self.assertEquals(self.controller.userNS['userns1'], 1)
60 self.assertEquals(self.controller.userNS['userns2'], 2)
55 self.assertEquals(self.controller.userNS['userns2'], 2)
61
56
62 self.controller.execute(code).addCallback(testControllerUserNS)
57 self.controller.execute(code).addCallback(testControllerUserNS)
63
58
64
59
65 def testControllerInstantiatesIEngine(self):
60 def testControllerInstantiatesIEngine(self):
66 self.assert_(es.IEngineBase.providedBy(self.controller.engine))
61 self.assert_(es.IEngineBase.providedBy(self.controller.engine))
67
62
68 def testControllerCompletesToken(self):
63 def testControllerCompletesToken(self):
69 code = """longNameVariable=10"""
64 code = """longNameVariable=10"""
70 def testCompletes(result):
65 def testCompletes(result):
71 self.assert_("longNameVariable" in result)
66 self.assert_("longNameVariable" in result)
72
67
73 def testCompleteToken(result):
68 def testCompleteToken(result):
74 self.controller.complete("longNa").addCallback(testCompletes)
69 self.controller.complete("longNa").addCallback(testCompletes)
75
70
76 self.controller.execute(code).addCallback(testCompletes)
71 self.controller.execute(code).addCallback(testCompletes)
77
72
@@ -1,326 +1,352 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
2 """
3 """
3 frontendbase provides an interface and base class for GUI frontends for IPython.kernel/IPython.kernel.core.
4 frontendbase provides an interface and base class for GUI frontends for
5 IPython.kernel/IPython.kernel.core.
4
6
5 Frontend implementations will likely want to subclass FrontEndBase.
7 Frontend implementations will likely want to subclass FrontEndBase.
6
8
7 Author: Barry Wark
9 Author: Barry Wark
8 """
10 """
9 __docformat__ = "restructuredtext en"
11 __docformat__ = "restructuredtext en"
10
12
11 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
13 #
15 #
14 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
17
19
18 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
19 # Imports
21 # Imports
20 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
21 import string
23 import string
22 import uuid
24 import uuid
23 import _ast
25 import _ast
24
26
25 import zope.interface as zi
27 import zope.interface as zi
26
28
27 from IPython.kernel.core.history import FrontEndHistory
29 from IPython.kernel.core.history import FrontEndHistory
28 from IPython.kernel.core.util import Bunch
30 from IPython.kernel.core.util import Bunch
29 from IPython.kernel.engineservice import IEngineCore
31 from IPython.kernel.engineservice import IEngineCore
30
32
31 from twisted.python.failure import Failure
33 from twisted.python.failure import Failure
32
34
33 ##############################################################################
35 ##############################################################################
34 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
36 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
35 # not
37 # not
36
38
37 rc = Bunch()
39 rc = Bunch()
38 rc.prompt_in1 = r'In [$number]: '
40 rc.prompt_in1 = r'In [$number]: '
39 rc.prompt_in2 = r'...'
41 rc.prompt_in2 = r'...'
40 rc.prompt_out = r'Out [$number]: '
42 rc.prompt_out = r'Out [$number]: '
41
43
42 ##############################################################################
44 ##############################################################################
43
45
44 class IFrontEndFactory(zi.Interface):
46 class IFrontEndFactory(zi.Interface):
45 """Factory interface for frontends."""
47 """Factory interface for frontends."""
46
48
47 def __call__(engine=None, history=None):
49 def __call__(engine=None, history=None):
48 """
50 """
49 Parameters:
51 Parameters:
50 interpreter : IPython.kernel.engineservice.IEngineCore
52 interpreter : IPython.kernel.engineservice.IEngineCore
51 """
53 """
52
54
53 pass
55 pass
54
56
55
57
56
58
57 class IFrontEnd(zi.Interface):
59 class IFrontEnd(zi.Interface):
58 """Interface for frontends. All methods return t.i.d.Deferred"""
60 """Interface for frontends. All methods return t.i.d.Deferred"""
59
61
60 zi.Attribute("input_prompt_template", "string.Template instance substituteable with execute result.")
62 zi.Attribute("input_prompt_template", "string.Template instance\
61 zi.Attribute("output_prompt_template", "string.Template instance substituteable with execute result.")
63 substituteable with execute result.")
62 zi.Attribute("continuation_prompt_template", "string.Template instance substituteable with execute result.")
64 zi.Attribute("output_prompt_template", "string.Template instance\
65 substituteable with execute result.")
66 zi.Attribute("continuation_prompt_template", "string.Template instance\
67 substituteable with execute result.")
63
68
64 def update_cell_prompt(self, result):
69 def update_cell_prompt(self, result):
65 """Subclass may override to update the input prompt for a block.
70 """Subclass may override to update the input prompt for a block.
66 Since this method will be called as a twisted.internet.defer.Deferred's callback,
71 Since this method will be called as a
67 implementations should return result when finished."""
72 twisted.internet.defer.Deferred's callback,
73 implementations should return result when finished.
74
75 NB: result is a failure if the execute returned a failre.
76 To get the blockID, you should do something like::
77 if(isinstance(result, twisted.python.failure.Failure)):
78 blockID = result.blockID
79 else:
80 blockID = result['blockID']
81 """
68
82
69 pass
83 pass
70
84
71 def render_result(self, result):
85 def render_result(self, result):
72 """Render the result of an execute call. Implementors may choose the method of rendering.
86 """Render the result of an execute call. Implementors may choose the
73 For example, a notebook-style frontend might render a Chaco plot inline.
87 method of rendering.
88 For example, a notebook-style frontend might render a Chaco plot
89 inline.
74
90
75 Parameters:
91 Parameters:
76 result : dict (result of IEngineBase.execute )
92 result : dict (result of IEngineBase.execute )
77
93
78 Result:
94 Result:
79 Output of frontend rendering
95 Output of frontend rendering
80 """
96 """
81
97
82 pass
98 pass
83
99
84 def render_error(self, failure):
100 def render_error(self, failure):
85 """Subclasses must override to render the failure. Since this method will be called as a
101 """Subclasses must override to render the failure. Since this method
86 twisted.internet.defer.Deferred's callback, implementations should return result
102 ill be called as a twisted.internet.defer.Deferred's callback,
87 when finished."""
103 implementations should return result when finished.
104 """
88
105
89 pass
106 pass
90
107
91
108
92 def input_prompt(result={}):
109 def input_prompt(result={}):
93 """Returns the input prompt by subsituting into self.input_prompt_template"""
110 """Returns the input prompt by subsituting into
111 self.input_prompt_template
112 """
94 pass
113 pass
95
114
96 def output_prompt(result):
115 def output_prompt(result):
97 """Returns the output prompt by subsituting into self.output_prompt_template"""
116 """Returns the output prompt by subsituting into
117 self.output_prompt_template
118 """
98
119
99 pass
120 pass
100
121
101 def continuation_prompt():
122 def continuation_prompt():
102 """Returns the continuation prompt by subsituting into self.continuation_prompt_template"""
123 """Returns the continuation prompt by subsituting into
124 self.continuation_prompt_template
125 """
103
126
104 pass
127 pass
105
128
106 def is_complete(block):
129 def is_complete(block):
107 """Returns True if block is complete, False otherwise."""
130 """Returns True if block is complete, False otherwise."""
108
131
109 pass
132 pass
110
133
111 def compile_ast(block):
134 def compile_ast(block):
112 """Compiles block to an _ast.AST"""
135 """Compiles block to an _ast.AST"""
113
136
114 pass
137 pass
115
138
116
139
117 def get_history_previous(currentBlock):
140 def get_history_previous(currentBlock):
118 """Returns the block previous in the history. Saves currentBlock if
141 """Returns the block previous in the history. Saves currentBlock if
119 the history_cursor is currently at the end of the input history"""
142 the history_cursor is currently at the end of the input history"""
120 pass
143 pass
121
144
122 def get_history_next():
145 def get_history_next():
123 """Returns the next block in the history."""
146 """Returns the next block in the history."""
124
147
125 pass
148 pass
126
149
127
150
128 class FrontEndBase(object):
151 class FrontEndBase(object):
129 """
152 """
130 FrontEndBase manages the state tasks for a CLI frontend:
153 FrontEndBase manages the state tasks for a CLI frontend:
131 - Input and output history management
154 - Input and output history management
132 - Input/continuation and output prompt generation
155 - Input/continuation and output prompt generation
133
156
134 Some issues (due to possibly unavailable engine):
157 Some issues (due to possibly unavailable engine):
135 - How do we get the current cell number for the engine?
158 - How do we get the current cell number for the engine?
136 - How do we handle completions?
159 - How do we handle completions?
137 """
160 """
138
161
139 zi.implements(IFrontEnd)
162 zi.implements(IFrontEnd)
140 zi.classProvides(IFrontEndFactory)
163 zi.classProvides(IFrontEndFactory)
141
164
142 history_cursor = 0
165 history_cursor = 0
143
166
144 current_indent_level = 0
167 current_indent_level = 0
145
168
146
169
147 input_prompt_template = string.Template(rc.prompt_in1)
170 input_prompt_template = string.Template(rc.prompt_in1)
148 output_prompt_template = string.Template(rc.prompt_out)
171 output_prompt_template = string.Template(rc.prompt_out)
149 continuation_prompt_template = string.Template(rc.prompt_in2)
172 continuation_prompt_template = string.Template(rc.prompt_in2)
150
173
151 def __init__(self, engine=None, history=None):
174 def __init__(self, engine=None, history=None):
152 assert(engine==None or IEngineCore.providedBy(engine))
175 assert(engine==None or IEngineCore.providedBy(engine))
153 self.engine = IEngineCore(engine)
176 self.engine = IEngineCore(engine)
154 if history is None:
177 if history is None:
155 self.history = FrontEndHistory(input_cache=[''])
178 self.history = FrontEndHistory(input_cache=[''])
156 else:
179 else:
157 self.history = history
180 self.history = history
158
181
159
182
160 def input_prompt(self, result={}):
183 def input_prompt(self, result={}):
161 """Returns the current input prompt
184 """Returns the current input prompt
162
185
163 It would be great to use ipython1.core.prompts.Prompt1 here
186 It would be great to use ipython1.core.prompts.Prompt1 here
164 """
187 """
165
188
166 result.setdefault('number','')
189 result.setdefault('number','')
167
190
168 return self.input_prompt_template.safe_substitute(result)
191 return self.input_prompt_template.safe_substitute(result)
169
192
170
193
171 def continuation_prompt(self):
194 def continuation_prompt(self):
172 """Returns the current continuation prompt"""
195 """Returns the current continuation prompt"""
173
196
174 return self.continuation_prompt_template.safe_substitute()
197 return self.continuation_prompt_template.safe_substitute()
175
198
176 def output_prompt(self, result):
199 def output_prompt(self, result):
177 """Returns the output prompt for result"""
200 """Returns the output prompt for result"""
178
201
179 return self.output_prompt_template.safe_substitute(result)
202 return self.output_prompt_template.safe_substitute(result)
180
203
181
204
182 def is_complete(self, block):
205 def is_complete(self, block):
183 """Determine if block is complete.
206 """Determine if block is complete.
184
207
185 Parameters
208 Parameters
186 block : string
209 block : string
187
210
188 Result
211 Result
189 True if block can be sent to the engine without compile errors.
212 True if block can be sent to the engine without compile errors.
190 False otherwise.
213 False otherwise.
191 """
214 """
192
215
193 try:
216 try:
194 ast = self.compile_ast(block)
217 ast = self.compile_ast(block)
195 except:
218 except:
196 return False
219 return False
197
220
198 lines = block.split('\n')
221 lines = block.split('\n')
199 return (len(lines)==1 or str(lines[-1])=='')
222 return (len(lines)==1 or str(lines[-1])=='')
200
223
201
224
202 def compile_ast(self, block):
225 def compile_ast(self, block):
203 """Compile block to an AST
226 """Compile block to an AST
204
227
205 Parameters:
228 Parameters:
206 block : str
229 block : str
207
230
208 Result:
231 Result:
209 AST
232 AST
210
233
211 Throws:
234 Throws:
212 Exception if block cannot be compiled
235 Exception if block cannot be compiled
213 """
236 """
214
237
215 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
238 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
216
239
217
240
218 def execute(self, block, blockID=None):
241 def execute(self, block, blockID=None):
219 """Execute the block and return result.
242 """Execute the block and return result.
220
243
221 Parameters:
244 Parameters:
222 block : {str, AST}
245 block : {str, AST}
223 blockID : any
246 blockID : any
224 Caller may provide an ID to identify this block. result['blockID'] := blockID
247 Caller may provide an ID to identify this block.
248 result['blockID'] := blockID
225
249
226 Result:
250 Result:
227 Deferred result of self.interpreter.execute
251 Deferred result of self.interpreter.execute
228 """
252 """
229
253
230 if(not self.is_complete(block)):
254 if(not self.is_complete(block)):
231 return Failure(Exception("Block is not compilable"))
255 return Failure(Exception("Block is not compilable"))
232
256
233 if(blockID == None):
257 if(blockID == None):
234 blockID = uuid.uuid4() #random UUID
258 blockID = uuid.uuid4() #random UUID
235
259
236 d = self.engine.execute(block)
260 d = self.engine.execute(block)
237 d.addCallback(self._add_history, block=block)
261 d.addCallback(self._add_history, block=block)
238 d.addBoth(self._add_block_id, blockID)
262 d.addBoth(self._add_block_id, blockID)
239 d.addBoth(self.update_cell_prompt)
263 d.addBoth(self.update_cell_prompt)
240 d.addCallbacks(self.render_result, errback=self.render_error)
264 d.addCallbacks(self.render_result, errback=self.render_error)
241
265
242 return d
266 return d
243
267
244
268
245 def _add_block_id(self, result, blockID):
269 def _add_block_id(self, result, blockID):
246 """Add the blockID to result or failure. Unfortunatley, we have to treat failures
270 """Add the blockID to result or failure. Unfortunatley, we have to
247 differently than result dicts
271 treat failures differently than result dicts.
248 """
272 """
249
273
250 if(isinstance(result, Failure)):
274 if(isinstance(result, Failure)):
251 result.blockID = blockID
275 result.blockID = blockID
252 else:
276 else:
253 result['blockID'] = blockID
277 result['blockID'] = blockID
254
278
255 return result
279 return result
256
280
257 def _add_history(self, result, block=None):
281 def _add_history(self, result, block=None):
258 """Add block to the history"""
282 """Add block to the history"""
259
283
260 assert(block != None)
284 assert(block != None)
261 self.history.add_items([block])
285 self.history.add_items([block])
262 self.history_cursor += 1
286 self.history_cursor += 1
263
287
264 return result
288 return result
265
289
266
290
267 def get_history_previous(self, currentBlock):
291 def get_history_previous(self, currentBlock):
268 """ Returns previous history string and decrement history cursor.
292 """ Returns previous history string and decrement history cursor.
269 """
293 """
270 command = self.history.get_history_item(self.history_cursor - 1)
294 command = self.history.get_history_item(self.history_cursor - 1)
271
295
272 if command is not None:
296 if command is not None:
273 if(self.history_cursor == len(self.history.input_cache)):
297 if(self.history_cursor == len(self.history.input_cache)):
274 self.history.input_cache[self.history_cursor] = currentBlock
298 self.history.input_cache[self.history_cursor] = currentBlock
275 self.history_cursor -= 1
299 self.history_cursor -= 1
276 return command
300 return command
277
301
278
302
279 def get_history_next(self):
303 def get_history_next(self):
280 """ Returns next history string and increment history cursor.
304 """ Returns next history string and increment history cursor.
281 """
305 """
282 command = self.history.get_history_item(self.history_cursor+1)
306 command = self.history.get_history_item(self.history_cursor+1)
283
307
284 if command is not None:
308 if command is not None:
285 self.history_cursor += 1
309 self.history_cursor += 1
286 return command
310 return command
287
311
288 ###
312 ###
289 # Subclasses probably want to override these methods...
313 # Subclasses probably want to override these methods...
290 ###
314 ###
291
315
292 def update_cell_prompt(self, result):
316 def update_cell_prompt(self, result):
293 """Subclass may override to update the input prompt for a block.
317 """Subclass may override to update the input prompt for a block.
294 Since this method will be called as a twisted.internet.defer.Deferred's callback,
318 Since this method will be called as a
295 implementations should return result when finished.
319 twisted.internet.defer.Deferred's callback, implementations should
320 return result when finished.
296
321
297 NP: result is a failure if the execute returned a failre. To get the blockID, you should
322 NB: result is a failure if the execute returned a failre.
298 do something like::
323 To get the blockID, you should do something like::
299 if(isinstance(result, twisted.python.failure.Failure)):
324 if(isinstance(result, twisted.python.failure.Failure)):
300 blockID = result.blockID
325 blockID = result.blockID
301 else:
326 else:
302 blockID = result['blockID']
327 blockID = result['blockID']
303
328
304
329
305 """
330 """
306
331
307 return result
332 return result
308
333
309
334
310 def render_result(self, result):
335 def render_result(self, result):
311 """Subclasses must override to render result. Since this method will be called as a
336 """Subclasses must override to render result. Since this method will
312 twisted.internet.defer.Deferred's callback, implementations should return result
337 be called as a twisted.internet.defer.Deferred's callback,
313 when finished."""
338 implementations should return result when finished.
339 """
314
340
315 return result
341 return result
316
342
317
343
318 def render_error(self, failure):
344 def render_error(self, failure):
319 """Subclasses must override to render the failure. Since this method will be called as a
345 """Subclasses must override to render the failure. Since this method
320 twisted.internet.defer.Deferred's callback, implementations should return result
346 will be called as a twisted.internet.defer.Deferred's callback,
321 when finished."""
347 implementations should return result when finished."""
322
348
323 return failure
349 return failure
324
350
325
351
326
352
@@ -1,149 +1,151 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """This file contains unittests for the frontendbase module."""
3 """This file contains unittests for the frontendbase module."""
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #-------------------------------------------------------------------------------
7 #---------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
12 #---------------------------------------------------------------------------
13
13
14 #-------------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #---------------------------------------------------------------------------
17
17
18 import unittest
18 import unittest
19 from IPython.frontend import frontendbase
19 from IPython.frontend import frontendbase
20 from IPython.kernel.engineservice import EngineService
20 from IPython.kernel.engineservice import EngineService
21
21
22 class FrontEndCallbackChecker(frontendbase.FrontEndBase):
22 class FrontEndCallbackChecker(frontendbase.FrontEndBase):
23 """FrontEndBase subclass for checking callbacks"""
23 """FrontEndBase subclass for checking callbacks"""
24 def __init__(self, engine=None, history=None):
24 def __init__(self, engine=None, history=None):
25 super(FrontEndCallbackChecker, self).__init__(engine=engine, history=history)
25 super(FrontEndCallbackChecker, self).__init__(engine=engine,
26 history=history)
26 self.updateCalled = False
27 self.updateCalled = False
27 self.renderResultCalled = False
28 self.renderResultCalled = False
28 self.renderErrorCalled = False
29 self.renderErrorCalled = False
29
30
30 def update_cell_prompt(self, result):
31 def update_cell_prompt(self, result):
31 self.updateCalled = True
32 self.updateCalled = True
32 return result
33 return result
33
34
34 def render_result(self, result):
35 def render_result(self, result):
35 self.renderResultCalled = True
36 self.renderResultCalled = True
36 return result
37 return result
37
38
38
39
39 def render_error(self, failure):
40 def render_error(self, failure):
40 self.renderErrorCalled = True
41 self.renderErrorCalled = True
41 return failure
42 return failure
42
43
43
44
44
45
45
46
46 class TestFrontendBase(unittest.TestCase):
47 class TestFrontendBase(unittest.TestCase):
47 def setUp(self):
48 def setUp(self):
48 """Setup the EngineService and FrontEndBase"""
49 """Setup the EngineService and FrontEndBase"""
49
50
50 self.fb = FrontEndCallbackChecker(engine=EngineService())
51 self.fb = FrontEndCallbackChecker(engine=EngineService())
51
52
52
53
53 def test_implements_IFrontEnd(self):
54 def test_implements_IFrontEnd(self):
54 assert(frontendbase.IFrontEnd.implementedBy(frontendbase.FrontEndBase))
55 assert(frontendbase.IFrontEnd.implementedBy(
56 frontendbase.FrontEndBase))
55
57
56
58
57 def test_is_complete_returns_False_for_incomplete_block(self):
59 def test_is_complete_returns_False_for_incomplete_block(self):
58 """"""
60 """"""
59
61
60 block = """def test(a):"""
62 block = """def test(a):"""
61
63
62 assert(self.fb.is_complete(block) == False)
64 assert(self.fb.is_complete(block) == False)
63
65
64 def test_is_complete_returns_True_for_complete_block(self):
66 def test_is_complete_returns_True_for_complete_block(self):
65 """"""
67 """"""
66
68
67 block = """def test(a): pass"""
69 block = """def test(a): pass"""
68
70
69 assert(self.fb.is_complete(block))
71 assert(self.fb.is_complete(block))
70
72
71 block = """a=3"""
73 block = """a=3"""
72
74
73 assert(self.fb.is_complete(block))
75 assert(self.fb.is_complete(block))
74
76
75
77
76 def test_blockID_added_to_result(self):
78 def test_blockID_added_to_result(self):
77 block = """3+3"""
79 block = """3+3"""
78
80
79 d = self.fb.execute(block, blockID='TEST_ID')
81 d = self.fb.execute(block, blockID='TEST_ID')
80
82
81 d.addCallback(self.checkBlockID, expected='TEST_ID')
83 d.addCallback(self.checkBlockID, expected='TEST_ID')
82
84
83 def test_blockID_added_to_failure(self):
85 def test_blockID_added_to_failure(self):
84 block = "raise Exception()"
86 block = "raise Exception()"
85
87
86 d = self.fb.execute(block,blockID='TEST_ID')
88 d = self.fb.execute(block,blockID='TEST_ID')
87 d.addErrback(self.checkFailureID, expected='TEST_ID')
89 d.addErrback(self.checkFailureID, expected='TEST_ID')
88
90
89 def checkBlockID(self, result, expected=""):
91 def checkBlockID(self, result, expected=""):
90 assert(result['blockID'] == expected)
92 assert(result['blockID'] == expected)
91
93
92
94
93 def checkFailureID(self, failure, expected=""):
95 def checkFailureID(self, failure, expected=""):
94 assert(failure.blockID == expected)
96 assert(failure.blockID == expected)
95
97
96
98
97 def test_callbacks_added_to_execute(self):
99 def test_callbacks_added_to_execute(self):
98 """test that
100 """test that
99 update_cell_prompt
101 update_cell_prompt
100 render_result
102 render_result
101
103
102 are added to execute request
104 are added to execute request
103 """
105 """
104
106
105 d = self.fb.execute("10+10")
107 d = self.fb.execute("10+10")
106 d.addCallback(self.checkCallbacks)
108 d.addCallback(self.checkCallbacks)
107
109
108
110
109 def checkCallbacks(self, result):
111 def checkCallbacks(self, result):
110 assert(self.fb.updateCalled)
112 assert(self.fb.updateCalled)
111 assert(self.fb.renderResultCalled)
113 assert(self.fb.renderResultCalled)
112
114
113
115
114 def test_error_callback_added_to_execute(self):
116 def test_error_callback_added_to_execute(self):
115 """test that render_error called on execution error"""
117 """test that render_error called on execution error"""
116
118
117 d = self.fb.execute("raise Exception()")
119 d = self.fb.execute("raise Exception()")
118 d.addCallback(self.checkRenderError)
120 d.addCallback(self.checkRenderError)
119
121
120 def checkRenderError(self, result):
122 def checkRenderError(self, result):
121 assert(self.fb.renderErrorCalled)
123 assert(self.fb.renderErrorCalled)
122
124
123 def test_history_returns_expected_block(self):
125 def test_history_returns_expected_block(self):
124 """Make sure history browsing doesn't fail"""
126 """Make sure history browsing doesn't fail"""
125
127
126 blocks = ["a=1","a=2","a=3"]
128 blocks = ["a=1","a=2","a=3"]
127 for b in blocks:
129 for b in blocks:
128 d = self.fb.execute(b)
130 d = self.fb.execute(b)
129
131
130 # d is now the deferred for the last executed block
132 # d is now the deferred for the last executed block
131 d.addCallback(self.historyTests, blocks)
133 d.addCallback(self.historyTests, blocks)
132
134
133
135
134 def historyTests(self, result, blocks):
136 def historyTests(self, result, blocks):
135 """historyTests"""
137 """historyTests"""
136
138
137 assert(len(blocks) >= 3)
139 assert(len(blocks) >= 3)
138 assert(self.fb.get_history_previous("") == blocks[-2])
140 assert(self.fb.get_history_previous("") == blocks[-2])
139 assert(self.fb.get_history_previous("") == blocks[-3])
141 assert(self.fb.get_history_previous("") == blocks[-3])
140 assert(self.fb.get_history_next() == blocks[-2])
142 assert(self.fb.get_history_next() == blocks[-2])
141
143
142
144
143 def test_history_returns_none_at_startup(self):
145 def test_history_returns_none_at_startup(self):
144 """test_history_returns_none_at_startup"""
146 """test_history_returns_none_at_startup"""
145
147
146 assert(self.fb.get_history_previous("")==None)
148 assert(self.fb.get_history_previous("")==None)
147 assert(self.fb.get_history_next()==None)
149 assert(self.fb.get_history_next()==None)
148
150
149
151
@@ -1,2046 +1,2097 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 General purpose utilities.
3 General purpose utilities.
4
4
5 This is a grab-bag of stuff I find useful in most programs I write. Some of
5 This is a grab-bag of stuff I find useful in most programs I write. Some of
6 these things are also convenient when working at the command line.
6 these things are also convenient when working at the command line.
7
7
8 $Id: genutils.py 2998 2008-01-31 10:06:04Z vivainio $"""
8 $Id: genutils.py 2998 2008-01-31 10:06:04Z vivainio $"""
9
9
10 #*****************************************************************************
10 #*****************************************************************************
11 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
11 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
12 #
12 #
13 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
15 #*****************************************************************************
15 #*****************************************************************************
16
16
17 from IPython import Release
17 from IPython import Release
18 __author__ = '%s <%s>' % Release.authors['Fernando']
18 __author__ = '%s <%s>' % Release.authors['Fernando']
19 __license__ = Release.license
19 __license__ = Release.license
20
20
21 #****************************************************************************
21 #****************************************************************************
22 # required modules from the Python standard library
22 # required modules from the Python standard library
23 import __main__
23 import __main__
24 import commands
24 import commands
25 try:
25 try:
26 import doctest
26 import doctest
27 except ImportError:
27 except ImportError:
28 pass
28 pass
29 import os
29 import os
30 import platform
30 import re
31 import re
31 import shlex
32 import shlex
32 import shutil
33 import shutil
34 import subprocess
33 import sys
35 import sys
34 import tempfile
36 import tempfile
35 import time
37 import time
36 import types
38 import types
37 import warnings
39 import warnings
38
40
39 # Curses and termios are Unix-only modules
41 # Curses and termios are Unix-only modules
40 try:
42 try:
41 import curses
43 import curses
42 # We need termios as well, so if its import happens to raise, we bail on
44 # We need termios as well, so if its import happens to raise, we bail on
43 # using curses altogether.
45 # using curses altogether.
44 import termios
46 import termios
45 except ImportError:
47 except ImportError:
46 USE_CURSES = False
48 USE_CURSES = False
47 else:
49 else:
48 # Curses on Solaris may not be complete, so we can't use it there
50 # Curses on Solaris may not be complete, so we can't use it there
49 USE_CURSES = hasattr(curses,'initscr')
51 USE_CURSES = hasattr(curses,'initscr')
50
52
51 # Other IPython utilities
53 # Other IPython utilities
52 import IPython
54 import IPython
53 from IPython.Itpl import Itpl,itpl,printpl
55 from IPython.Itpl import Itpl,itpl,printpl
54 from IPython import DPyGetOpt, platutils
56 from IPython import DPyGetOpt, platutils
55 from IPython.generics import result_display
57 from IPython.generics import result_display
56 import IPython.ipapi
58 import IPython.ipapi
57 from IPython.external.path import path
59 from IPython.external.path import path
58 if os.name == "nt":
60 if os.name == "nt":
59 from IPython.winconsole import get_console_size
61 from IPython.winconsole import get_console_size
60
62
61 try:
63 try:
62 set
64 set
63 except:
65 except:
64 from sets import Set as set
66 from sets import Set as set
65
67
66
68
67 #****************************************************************************
69 #****************************************************************************
68 # Exceptions
70 # Exceptions
69 class Error(Exception):
71 class Error(Exception):
70 """Base class for exceptions in this module."""
72 """Base class for exceptions in this module."""
71 pass
73 pass
72
74
73 #----------------------------------------------------------------------------
75 #----------------------------------------------------------------------------
74 class IOStream:
76 class IOStream:
75 def __init__(self,stream,fallback):
77 def __init__(self,stream,fallback):
76 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
78 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
77 stream = fallback
79 stream = fallback
78 self.stream = stream
80 self.stream = stream
79 self._swrite = stream.write
81 self._swrite = stream.write
80 self.flush = stream.flush
82 self.flush = stream.flush
81
83
82 def write(self,data):
84 def write(self,data):
83 try:
85 try:
84 self._swrite(data)
86 self._swrite(data)
85 except:
87 except:
86 try:
88 try:
87 # print handles some unicode issues which may trip a plain
89 # print handles some unicode issues which may trip a plain
88 # write() call. Attempt to emulate write() by using a
90 # write() call. Attempt to emulate write() by using a
89 # trailing comma
91 # trailing comma
90 print >> self.stream, data,
92 print >> self.stream, data,
91 except:
93 except:
92 # if we get here, something is seriously broken.
94 # if we get here, something is seriously broken.
93 print >> sys.stderr, \
95 print >> sys.stderr, \
94 'ERROR - failed to write data to stream:', self.stream
96 'ERROR - failed to write data to stream:', self.stream
95
97
96 def close(self):
98 def close(self):
97 pass
99 pass
98
100
99
101
100 class IOTerm:
102 class IOTerm:
101 """ Term holds the file or file-like objects for handling I/O operations.
103 """ Term holds the file or file-like objects for handling I/O operations.
102
104
103 These are normally just sys.stdin, sys.stdout and sys.stderr but for
105 These are normally just sys.stdin, sys.stdout and sys.stderr but for
104 Windows they can can replaced to allow editing the strings before they are
106 Windows they can can replaced to allow editing the strings before they are
105 displayed."""
107 displayed."""
106
108
107 # In the future, having IPython channel all its I/O operations through
109 # In the future, having IPython channel all its I/O operations through
108 # this class will make it easier to embed it into other environments which
110 # this class will make it easier to embed it into other environments which
109 # are not a normal terminal (such as a GUI-based shell)
111 # are not a normal terminal (such as a GUI-based shell)
110 def __init__(self,cin=None,cout=None,cerr=None):
112 def __init__(self,cin=None,cout=None,cerr=None):
111 self.cin = IOStream(cin,sys.stdin)
113 self.cin = IOStream(cin,sys.stdin)
112 self.cout = IOStream(cout,sys.stdout)
114 self.cout = IOStream(cout,sys.stdout)
113 self.cerr = IOStream(cerr,sys.stderr)
115 self.cerr = IOStream(cerr,sys.stderr)
114
116
115 # Global variable to be used for all I/O
117 # Global variable to be used for all I/O
116 Term = IOTerm()
118 Term = IOTerm()
117
119
118 import IPython.rlineimpl as readline
120 import IPython.rlineimpl as readline
119 # Remake Term to use the readline i/o facilities
121 # Remake Term to use the readline i/o facilities
120 if sys.platform == 'win32' and readline.have_readline:
122 if sys.platform == 'win32' and readline.have_readline:
121
123
122 Term = IOTerm(cout=readline._outputfile,cerr=readline._outputfile)
124 Term = IOTerm(cout=readline._outputfile,cerr=readline._outputfile)
123
125
124
126
125 #****************************************************************************
127 #****************************************************************************
126 # Generic warning/error printer, used by everything else
128 # Generic warning/error printer, used by everything else
127 def warn(msg,level=2,exit_val=1):
129 def warn(msg,level=2,exit_val=1):
128 """Standard warning printer. Gives formatting consistency.
130 """Standard warning printer. Gives formatting consistency.
129
131
130 Output is sent to Term.cerr (sys.stderr by default).
132 Output is sent to Term.cerr (sys.stderr by default).
131
133
132 Options:
134 Options:
133
135
134 -level(2): allows finer control:
136 -level(2): allows finer control:
135 0 -> Do nothing, dummy function.
137 0 -> Do nothing, dummy function.
136 1 -> Print message.
138 1 -> Print message.
137 2 -> Print 'WARNING:' + message. (Default level).
139 2 -> Print 'WARNING:' + message. (Default level).
138 3 -> Print 'ERROR:' + message.
140 3 -> Print 'ERROR:' + message.
139 4 -> Print 'FATAL ERROR:' + message and trigger a sys.exit(exit_val).
141 4 -> Print 'FATAL ERROR:' + message and trigger a sys.exit(exit_val).
140
142
141 -exit_val (1): exit value returned by sys.exit() for a level 4
143 -exit_val (1): exit value returned by sys.exit() for a level 4
142 warning. Ignored for all other levels."""
144 warning. Ignored for all other levels."""
143
145
144 if level>0:
146 if level>0:
145 header = ['','','WARNING: ','ERROR: ','FATAL ERROR: ']
147 header = ['','','WARNING: ','ERROR: ','FATAL ERROR: ']
146 print >> Term.cerr, '%s%s' % (header[level],msg)
148 print >> Term.cerr, '%s%s' % (header[level],msg)
147 if level == 4:
149 if level == 4:
148 print >> Term.cerr,'Exiting.\n'
150 print >> Term.cerr,'Exiting.\n'
149 sys.exit(exit_val)
151 sys.exit(exit_val)
150
152
151 def info(msg):
153 def info(msg):
152 """Equivalent to warn(msg,level=1)."""
154 """Equivalent to warn(msg,level=1)."""
153
155
154 warn(msg,level=1)
156 warn(msg,level=1)
155
157
156 def error(msg):
158 def error(msg):
157 """Equivalent to warn(msg,level=3)."""
159 """Equivalent to warn(msg,level=3)."""
158
160
159 warn(msg,level=3)
161 warn(msg,level=3)
160
162
161 def fatal(msg,exit_val=1):
163 def fatal(msg,exit_val=1):
162 """Equivalent to warn(msg,exit_val=exit_val,level=4)."""
164 """Equivalent to warn(msg,exit_val=exit_val,level=4)."""
163
165
164 warn(msg,exit_val=exit_val,level=4)
166 warn(msg,exit_val=exit_val,level=4)
165
167
166 #---------------------------------------------------------------------------
168 #---------------------------------------------------------------------------
167 # Debugging routines
169 # Debugging routines
168 #
170 #
169 def debugx(expr,pre_msg=''):
171 def debugx(expr,pre_msg=''):
170 """Print the value of an expression from the caller's frame.
172 """Print the value of an expression from the caller's frame.
171
173
172 Takes an expression, evaluates it in the caller's frame and prints both
174 Takes an expression, evaluates it in the caller's frame and prints both
173 the given expression and the resulting value (as well as a debug mark
175 the given expression and the resulting value (as well as a debug mark
174 indicating the name of the calling function. The input must be of a form
176 indicating the name of the calling function. The input must be of a form
175 suitable for eval().
177 suitable for eval().
176
178
177 An optional message can be passed, which will be prepended to the printed
179 An optional message can be passed, which will be prepended to the printed
178 expr->value pair."""
180 expr->value pair."""
179
181
180 cf = sys._getframe(1)
182 cf = sys._getframe(1)
181 print '[DBG:%s] %s%s -> %r' % (cf.f_code.co_name,pre_msg,expr,
183 print '[DBG:%s] %s%s -> %r' % (cf.f_code.co_name,pre_msg,expr,
182 eval(expr,cf.f_globals,cf.f_locals))
184 eval(expr,cf.f_globals,cf.f_locals))
183
185
184 # deactivate it by uncommenting the following line, which makes it a no-op
186 # deactivate it by uncommenting the following line, which makes it a no-op
185 #def debugx(expr,pre_msg=''): pass
187 #def debugx(expr,pre_msg=''): pass
186
188
187 #----------------------------------------------------------------------------
189 #----------------------------------------------------------------------------
188 StringTypes = types.StringTypes
190 StringTypes = types.StringTypes
189
191
190 # Basic timing functionality
192 # Basic timing functionality
191
193
192 # If possible (Unix), use the resource module instead of time.clock()
194 # If possible (Unix), use the resource module instead of time.clock()
193 try:
195 try:
194 import resource
196 import resource
195 def clocku():
197 def clocku():
196 """clocku() -> floating point number
198 """clocku() -> floating point number
197
199
198 Return the *USER* CPU time in seconds since the start of the process.
200 Return the *USER* CPU time in seconds since the start of the process.
199 This is done via a call to resource.getrusage, so it avoids the
201 This is done via a call to resource.getrusage, so it avoids the
200 wraparound problems in time.clock()."""
202 wraparound problems in time.clock()."""
201
203
202 return resource.getrusage(resource.RUSAGE_SELF)[0]
204 return resource.getrusage(resource.RUSAGE_SELF)[0]
203
205
204 def clocks():
206 def clocks():
205 """clocks() -> floating point number
207 """clocks() -> floating point number
206
208
207 Return the *SYSTEM* CPU time in seconds since the start of the process.
209 Return the *SYSTEM* CPU time in seconds since the start of the process.
208 This is done via a call to resource.getrusage, so it avoids the
210 This is done via a call to resource.getrusage, so it avoids the
209 wraparound problems in time.clock()."""
211 wraparound problems in time.clock()."""
210
212
211 return resource.getrusage(resource.RUSAGE_SELF)[1]
213 return resource.getrusage(resource.RUSAGE_SELF)[1]
212
214
213 def clock():
215 def clock():
214 """clock() -> floating point number
216 """clock() -> floating point number
215
217
216 Return the *TOTAL USER+SYSTEM* CPU time in seconds since the start of
218 Return the *TOTAL USER+SYSTEM* CPU time in seconds since the start of
217 the process. This is done via a call to resource.getrusage, so it
219 the process. This is done via a call to resource.getrusage, so it
218 avoids the wraparound problems in time.clock()."""
220 avoids the wraparound problems in time.clock()."""
219
221
220 u,s = resource.getrusage(resource.RUSAGE_SELF)[:2]
222 u,s = resource.getrusage(resource.RUSAGE_SELF)[:2]
221 return u+s
223 return u+s
222
224
223 def clock2():
225 def clock2():
224 """clock2() -> (t_user,t_system)
226 """clock2() -> (t_user,t_system)
225
227
226 Similar to clock(), but return a tuple of user/system times."""
228 Similar to clock(), but return a tuple of user/system times."""
227 return resource.getrusage(resource.RUSAGE_SELF)[:2]
229 return resource.getrusage(resource.RUSAGE_SELF)[:2]
228
230
229 except ImportError:
231 except ImportError:
230 # There is no distinction of user/system time under windows, so we just use
232 # There is no distinction of user/system time under windows, so we just use
231 # time.clock() for everything...
233 # time.clock() for everything...
232 clocku = clocks = clock = time.clock
234 clocku = clocks = clock = time.clock
233 def clock2():
235 def clock2():
234 """Under windows, system CPU time can't be measured.
236 """Under windows, system CPU time can't be measured.
235
237
236 This just returns clock() and zero."""
238 This just returns clock() and zero."""
237 return time.clock(),0.0
239 return time.clock(),0.0
238
240
239 def timings_out(reps,func,*args,**kw):
241 def timings_out(reps,func,*args,**kw):
240 """timings_out(reps,func,*args,**kw) -> (t_total,t_per_call,output)
242 """timings_out(reps,func,*args,**kw) -> (t_total,t_per_call,output)
241
243
242 Execute a function reps times, return a tuple with the elapsed total
244 Execute a function reps times, return a tuple with the elapsed total
243 CPU time in seconds, the time per call and the function's output.
245 CPU time in seconds, the time per call and the function's output.
244
246
245 Under Unix, the return value is the sum of user+system time consumed by
247 Under Unix, the return value is the sum of user+system time consumed by
246 the process, computed via the resource module. This prevents problems
248 the process, computed via the resource module. This prevents problems
247 related to the wraparound effect which the time.clock() function has.
249 related to the wraparound effect which the time.clock() function has.
248
250
249 Under Windows the return value is in wall clock seconds. See the
251 Under Windows the return value is in wall clock seconds. See the
250 documentation for the time module for more details."""
252 documentation for the time module for more details."""
251
253
252 reps = int(reps)
254 reps = int(reps)
253 assert reps >=1, 'reps must be >= 1'
255 assert reps >=1, 'reps must be >= 1'
254 if reps==1:
256 if reps==1:
255 start = clock()
257 start = clock()
256 out = func(*args,**kw)
258 out = func(*args,**kw)
257 tot_time = clock()-start
259 tot_time = clock()-start
258 else:
260 else:
259 rng = xrange(reps-1) # the last time is executed separately to store output
261 rng = xrange(reps-1) # the last time is executed separately to store output
260 start = clock()
262 start = clock()
261 for dummy in rng: func(*args,**kw)
263 for dummy in rng: func(*args,**kw)
262 out = func(*args,**kw) # one last time
264 out = func(*args,**kw) # one last time
263 tot_time = clock()-start
265 tot_time = clock()-start
264 av_time = tot_time / reps
266 av_time = tot_time / reps
265 return tot_time,av_time,out
267 return tot_time,av_time,out
266
268
267 def timings(reps,func,*args,**kw):
269 def timings(reps,func,*args,**kw):
268 """timings(reps,func,*args,**kw) -> (t_total,t_per_call)
270 """timings(reps,func,*args,**kw) -> (t_total,t_per_call)
269
271
270 Execute a function reps times, return a tuple with the elapsed total CPU
272 Execute a function reps times, return a tuple with the elapsed total CPU
271 time in seconds and the time per call. These are just the first two values
273 time in seconds and the time per call. These are just the first two values
272 in timings_out()."""
274 in timings_out()."""
273
275
274 return timings_out(reps,func,*args,**kw)[0:2]
276 return timings_out(reps,func,*args,**kw)[0:2]
275
277
276 def timing(func,*args,**kw):
278 def timing(func,*args,**kw):
277 """timing(func,*args,**kw) -> t_total
279 """timing(func,*args,**kw) -> t_total
278
280
279 Execute a function once, return the elapsed total CPU time in
281 Execute a function once, return the elapsed total CPU time in
280 seconds. This is just the first value in timings_out()."""
282 seconds. This is just the first value in timings_out()."""
281
283
282 return timings_out(1,func,*args,**kw)[0]
284 return timings_out(1,func,*args,**kw)[0]
283
285
284 #****************************************************************************
286 #****************************************************************************
285 # file and system
287 # file and system
286
288
287 def arg_split(s,posix=False):
289 def arg_split(s,posix=False):
288 """Split a command line's arguments in a shell-like manner.
290 """Split a command line's arguments in a shell-like manner.
289
291
290 This is a modified version of the standard library's shlex.split()
292 This is a modified version of the standard library's shlex.split()
291 function, but with a default of posix=False for splitting, so that quotes
293 function, but with a default of posix=False for splitting, so that quotes
292 in inputs are respected."""
294 in inputs are respected."""
293
295
294 # XXX - there may be unicode-related problems here!!! I'm not sure that
296 # XXX - there may be unicode-related problems here!!! I'm not sure that
295 # shlex is truly unicode-safe, so it might be necessary to do
297 # shlex is truly unicode-safe, so it might be necessary to do
296 #
298 #
297 # s = s.encode(sys.stdin.encoding)
299 # s = s.encode(sys.stdin.encoding)
298 #
300 #
299 # first, to ensure that shlex gets a normal string. Input from anyone who
301 # first, to ensure that shlex gets a normal string. Input from anyone who
300 # knows more about unicode and shlex than I would be good to have here...
302 # knows more about unicode and shlex than I would be good to have here...
301 lex = shlex.shlex(s, posix=posix)
303 lex = shlex.shlex(s, posix=posix)
302 lex.whitespace_split = True
304 lex.whitespace_split = True
303 return list(lex)
305 return list(lex)
304
306
305 def system(cmd,verbose=0,debug=0,header=''):
307 def system(cmd,verbose=0,debug=0,header=''):
306 """Execute a system command, return its exit status.
308 """Execute a system command, return its exit status.
307
309
308 Options:
310 Options:
309
311
310 - verbose (0): print the command to be executed.
312 - verbose (0): print the command to be executed.
311
313
312 - debug (0): only print, do not actually execute.
314 - debug (0): only print, do not actually execute.
313
315
314 - header (''): Header to print on screen prior to the executed command (it
316 - header (''): Header to print on screen prior to the executed command (it
315 is only prepended to the command, no newlines are added).
317 is only prepended to the command, no newlines are added).
316
318
317 Note: a stateful version of this function is available through the
319 Note: a stateful version of this function is available through the
318 SystemExec class."""
320 SystemExec class."""
319
321
320 stat = 0
322 stat = 0
321 if verbose or debug: print header+cmd
323 if verbose or debug: print header+cmd
322 sys.stdout.flush()
324 sys.stdout.flush()
323 if not debug: stat = os.system(cmd)
325 if not debug: stat = os.system(cmd)
324 return stat
326 return stat
325
327
326 def abbrev_cwd():
328 def abbrev_cwd():
327 """ Return abbreviated version of cwd, e.g. d:mydir """
329 """ Return abbreviated version of cwd, e.g. d:mydir """
328 cwd = os.getcwd().replace('\\','/')
330 cwd = os.getcwd().replace('\\','/')
329 drivepart = ''
331 drivepart = ''
330 tail = cwd
332 tail = cwd
331 if sys.platform == 'win32':
333 if sys.platform == 'win32':
332 if len(cwd) < 4:
334 if len(cwd) < 4:
333 return cwd
335 return cwd
334 drivepart,tail = os.path.splitdrive(cwd)
336 drivepart,tail = os.path.splitdrive(cwd)
335
337
336
338
337 parts = tail.split('/')
339 parts = tail.split('/')
338 if len(parts) > 2:
340 if len(parts) > 2:
339 tail = '/'.join(parts[-2:])
341 tail = '/'.join(parts[-2:])
340
342
341 return (drivepart + (
343 return (drivepart + (
342 cwd == '/' and '/' or tail))
344 cwd == '/' and '/' or tail))
343
345
344
346
345 # This function is used by ipython in a lot of places to make system calls.
347 # This function is used by ipython in a lot of places to make system calls.
346 # We need it to be slightly different under win32, due to the vagaries of
348 # We need it to be slightly different under win32, due to the vagaries of
347 # 'network shares'. A win32 override is below.
349 # 'network shares'. A win32 override is below.
348
350
349 def shell(cmd,verbose=0,debug=0,header=''):
351 def shell(cmd,verbose=0,debug=0,header=''):
350 """Execute a command in the system shell, always return None.
352 """Execute a command in the system shell, always return None.
351
353
352 Options:
354 Options:
353
355
354 - verbose (0): print the command to be executed.
356 - verbose (0): print the command to be executed.
355
357
356 - debug (0): only print, do not actually execute.
358 - debug (0): only print, do not actually execute.
357
359
358 - header (''): Header to print on screen prior to the executed command (it
360 - header (''): Header to print on screen prior to the executed command (it
359 is only prepended to the command, no newlines are added).
361 is only prepended to the command, no newlines are added).
360
362
361 Note: this is similar to genutils.system(), but it returns None so it can
363 Note: this is similar to genutils.system(), but it returns None so it can
362 be conveniently used in interactive loops without getting the return value
364 be conveniently used in interactive loops without getting the return value
363 (typically 0) printed many times."""
365 (typically 0) printed many times."""
364
366
365 stat = 0
367 stat = 0
366 if verbose or debug: print header+cmd
368 if verbose or debug: print header+cmd
367 # flush stdout so we don't mangle python's buffering
369 # flush stdout so we don't mangle python's buffering
368 sys.stdout.flush()
370 sys.stdout.flush()
369
371
370 if not debug:
372 if not debug:
371 platutils.set_term_title("IPy " + cmd)
373 platutils.set_term_title("IPy " + cmd)
372 os.system(cmd)
374 os.system(cmd)
373 platutils.set_term_title("IPy " + abbrev_cwd())
375 platutils.set_term_title("IPy " + abbrev_cwd())
374
376
375 # override shell() for win32 to deal with network shares
377 # override shell() for win32 to deal with network shares
376 if os.name in ('nt','dos'):
378 if os.name in ('nt','dos'):
377
379
378 shell_ori = shell
380 shell_ori = shell
379
381
380 def shell(cmd,verbose=0,debug=0,header=''):
382 def shell(cmd,verbose=0,debug=0,header=''):
381 if os.getcwd().startswith(r"\\"):
383 if os.getcwd().startswith(r"\\"):
382 path = os.getcwd()
384 path = os.getcwd()
383 # change to c drive (cannot be on UNC-share when issuing os.system,
385 # change to c drive (cannot be on UNC-share when issuing os.system,
384 # as cmd.exe cannot handle UNC addresses)
386 # as cmd.exe cannot handle UNC addresses)
385 os.chdir("c:")
387 os.chdir("c:")
386 # issue pushd to the UNC-share and then run the command
388 # issue pushd to the UNC-share and then run the command
387 try:
389 try:
388 shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header)
390 shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header)
389 finally:
391 finally:
390 os.chdir(path)
392 os.chdir(path)
391 else:
393 else:
392 shell_ori(cmd,verbose,debug,header)
394 shell_ori(cmd,verbose,debug,header)
393
395
394 shell.__doc__ = shell_ori.__doc__
396 shell.__doc__ = shell_ori.__doc__
395
397
396 def getoutput(cmd,verbose=0,debug=0,header='',split=0):
398 def getoutput(cmd,verbose=0,debug=0,header='',split=0):
397 """Dummy substitute for perl's backquotes.
399 """Dummy substitute for perl's backquotes.
398
400
399 Executes a command and returns the output.
401 Executes a command and returns the output.
400
402
401 Accepts the same arguments as system(), plus:
403 Accepts the same arguments as system(), plus:
402
404
403 - split(0): if true, the output is returned as a list split on newlines.
405 - split(0): if true, the output is returned as a list split on newlines.
404
406
405 Note: a stateful version of this function is available through the
407 Note: a stateful version of this function is available through the
406 SystemExec class.
408 SystemExec class.
407
409
408 This is pretty much deprecated and rarely used,
410 This is pretty much deprecated and rarely used,
409 genutils.getoutputerror may be what you need.
411 genutils.getoutputerror may be what you need.
410
412
411 """
413 """
412
414
413 if verbose or debug: print header+cmd
415 if verbose or debug: print header+cmd
414 if not debug:
416 if not debug:
415 output = os.popen(cmd).read()
417 output = os.popen(cmd).read()
416 # stipping last \n is here for backwards compat.
418 # stipping last \n is here for backwards compat.
417 if output.endswith('\n'):
419 if output.endswith('\n'):
418 output = output[:-1]
420 output = output[:-1]
419 if split:
421 if split:
420 return output.split('\n')
422 return output.split('\n')
421 else:
423 else:
422 return output
424 return output
423
425
424 def getoutputerror(cmd,verbose=0,debug=0,header='',split=0):
426 def getoutputerror(cmd,verbose=0,debug=0,header='',split=0):
425 """Return (standard output,standard error) of executing cmd in a shell.
427 """Return (standard output,standard error) of executing cmd in a shell.
426
428
427 Accepts the same arguments as system(), plus:
429 Accepts the same arguments as system(), plus:
428
430
429 - split(0): if true, each of stdout/err is returned as a list split on
431 - split(0): if true, each of stdout/err is returned as a list split on
430 newlines.
432 newlines.
431
433
432 Note: a stateful version of this function is available through the
434 Note: a stateful version of this function is available through the
433 SystemExec class."""
435 SystemExec class."""
434
436
435 if verbose or debug: print header+cmd
437 if verbose or debug: print header+cmd
436 if not cmd:
438 if not cmd:
437 if split:
439 if split:
438 return [],[]
440 return [],[]
439 else:
441 else:
440 return '',''
442 return '',''
441 if not debug:
443 if not debug:
442 pin,pout,perr = os.popen3(cmd)
444 pin,pout,perr = os.popen3(cmd)
443 tout = pout.read().rstrip()
445 tout = pout.read().rstrip()
444 terr = perr.read().rstrip()
446 terr = perr.read().rstrip()
445 pin.close()
447 pin.close()
446 pout.close()
448 pout.close()
447 perr.close()
449 perr.close()
448 if split:
450 if split:
449 return tout.split('\n'),terr.split('\n')
451 return tout.split('\n'),terr.split('\n')
450 else:
452 else:
451 return tout,terr
453 return tout,terr
452
454
453 # for compatibility with older naming conventions
455 # for compatibility with older naming conventions
454 xsys = system
456 xsys = system
455 bq = getoutput
457 bq = getoutput
456
458
457 class SystemExec:
459 class SystemExec:
458 """Access the system and getoutput functions through a stateful interface.
460 """Access the system and getoutput functions through a stateful interface.
459
461
460 Note: here we refer to the system and getoutput functions from this
462 Note: here we refer to the system and getoutput functions from this
461 library, not the ones from the standard python library.
463 library, not the ones from the standard python library.
462
464
463 This class offers the system and getoutput functions as methods, but the
465 This class offers the system and getoutput functions as methods, but the
464 verbose, debug and header parameters can be set for the instance (at
466 verbose, debug and header parameters can be set for the instance (at
465 creation time or later) so that they don't need to be specified on each
467 creation time or later) so that they don't need to be specified on each
466 call.
468 call.
467
469
468 For efficiency reasons, there's no way to override the parameters on a
470 For efficiency reasons, there's no way to override the parameters on a
469 per-call basis other than by setting instance attributes. If you need
471 per-call basis other than by setting instance attributes. If you need
470 local overrides, it's best to directly call system() or getoutput().
472 local overrides, it's best to directly call system() or getoutput().
471
473
472 The following names are provided as alternate options:
474 The following names are provided as alternate options:
473 - xsys: alias to system
475 - xsys: alias to system
474 - bq: alias to getoutput
476 - bq: alias to getoutput
475
477
476 An instance can then be created as:
478 An instance can then be created as:
477 >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ')
479 >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ')
478
480
479 And used as:
481 And used as:
480 >>> sysexec.xsys('pwd')
482 >>> sysexec.xsys('pwd')
481 >>> dirlist = sysexec.bq('ls -l')
483 >>> dirlist = sysexec.bq('ls -l')
482 """
484 """
483
485
484 def __init__(self,verbose=0,debug=0,header='',split=0):
486 def __init__(self,verbose=0,debug=0,header='',split=0):
485 """Specify the instance's values for verbose, debug and header."""
487 """Specify the instance's values for verbose, debug and header."""
486 setattr_list(self,'verbose debug header split')
488 setattr_list(self,'verbose debug header split')
487
489
488 def system(self,cmd):
490 def system(self,cmd):
489 """Stateful interface to system(), with the same keyword parameters."""
491 """Stateful interface to system(), with the same keyword parameters."""
490
492
491 system(cmd,self.verbose,self.debug,self.header)
493 system(cmd,self.verbose,self.debug,self.header)
492
494
493 def shell(self,cmd):
495 def shell(self,cmd):
494 """Stateful interface to shell(), with the same keyword parameters."""
496 """Stateful interface to shell(), with the same keyword parameters."""
495
497
496 shell(cmd,self.verbose,self.debug,self.header)
498 shell(cmd,self.verbose,self.debug,self.header)
497
499
498 xsys = system # alias
500 xsys = system # alias
499
501
500 def getoutput(self,cmd):
502 def getoutput(self,cmd):
501 """Stateful interface to getoutput()."""
503 """Stateful interface to getoutput()."""
502
504
503 return getoutput(cmd,self.verbose,self.debug,self.header,self.split)
505 return getoutput(cmd,self.verbose,self.debug,self.header,self.split)
504
506
505 def getoutputerror(self,cmd):
507 def getoutputerror(self,cmd):
506 """Stateful interface to getoutputerror()."""
508 """Stateful interface to getoutputerror()."""
507
509
508 return getoutputerror(cmd,self.verbose,self.debug,self.header,self.split)
510 return getoutputerror(cmd,self.verbose,self.debug,self.header,self.split)
509
511
510 bq = getoutput # alias
512 bq = getoutput # alias
511
513
512 #-----------------------------------------------------------------------------
514 #-----------------------------------------------------------------------------
513 def mutex_opts(dict,ex_op):
515 def mutex_opts(dict,ex_op):
514 """Check for presence of mutually exclusive keys in a dict.
516 """Check for presence of mutually exclusive keys in a dict.
515
517
516 Call: mutex_opts(dict,[[op1a,op1b],[op2a,op2b]...]"""
518 Call: mutex_opts(dict,[[op1a,op1b],[op2a,op2b]...]"""
517 for op1,op2 in ex_op:
519 for op1,op2 in ex_op:
518 if op1 in dict and op2 in dict:
520 if op1 in dict and op2 in dict:
519 raise ValueError,'\n*** ERROR in Arguments *** '\
521 raise ValueError,'\n*** ERROR in Arguments *** '\
520 'Options '+op1+' and '+op2+' are mutually exclusive.'
522 'Options '+op1+' and '+op2+' are mutually exclusive.'
521
523
522 #-----------------------------------------------------------------------------
524 #-----------------------------------------------------------------------------
523 def get_py_filename(name):
525 def get_py_filename(name):
524 """Return a valid python filename in the current directory.
526 """Return a valid python filename in the current directory.
525
527
526 If the given name is not a file, it adds '.py' and searches again.
528 If the given name is not a file, it adds '.py' and searches again.
527 Raises IOError with an informative message if the file isn't found."""
529 Raises IOError with an informative message if the file isn't found."""
528
530
529 name = os.path.expanduser(name)
531 name = os.path.expanduser(name)
530 if not os.path.isfile(name) and not name.endswith('.py'):
532 if not os.path.isfile(name) and not name.endswith('.py'):
531 name += '.py'
533 name += '.py'
532 if os.path.isfile(name):
534 if os.path.isfile(name):
533 return name
535 return name
534 else:
536 else:
535 raise IOError,'File `%s` not found.' % name
537 raise IOError,'File `%s` not found.' % name
536
538
537 #-----------------------------------------------------------------------------
539 #-----------------------------------------------------------------------------
538 def filefind(fname,alt_dirs = None):
540 def filefind(fname,alt_dirs = None):
539 """Return the given filename either in the current directory, if it
541 """Return the given filename either in the current directory, if it
540 exists, or in a specified list of directories.
542 exists, or in a specified list of directories.
541
543
542 ~ expansion is done on all file and directory names.
544 ~ expansion is done on all file and directory names.
543
545
544 Upon an unsuccessful search, raise an IOError exception."""
546 Upon an unsuccessful search, raise an IOError exception."""
545
547
546 if alt_dirs is None:
548 if alt_dirs is None:
547 try:
549 try:
548 alt_dirs = get_home_dir()
550 alt_dirs = get_home_dir()
549 except HomeDirError:
551 except HomeDirError:
550 alt_dirs = os.getcwd()
552 alt_dirs = os.getcwd()
551 search = [fname] + list_strings(alt_dirs)
553 search = [fname] + list_strings(alt_dirs)
552 search = map(os.path.expanduser,search)
554 search = map(os.path.expanduser,search)
553 #print 'search list for',fname,'list:',search # dbg
555 #print 'search list for',fname,'list:',search # dbg
554 fname = search[0]
556 fname = search[0]
555 if os.path.isfile(fname):
557 if os.path.isfile(fname):
556 return fname
558 return fname
557 for direc in search[1:]:
559 for direc in search[1:]:
558 testname = os.path.join(direc,fname)
560 testname = os.path.join(direc,fname)
559 #print 'testname',testname # dbg
561 #print 'testname',testname # dbg
560 if os.path.isfile(testname):
562 if os.path.isfile(testname):
561 return testname
563 return testname
562 raise IOError,'File' + `fname` + \
564 raise IOError,'File' + `fname` + \
563 ' not found in current or supplied directories:' + `alt_dirs`
565 ' not found in current or supplied directories:' + `alt_dirs`
564
566
565 #----------------------------------------------------------------------------
567 #----------------------------------------------------------------------------
566 def file_read(filename):
568 def file_read(filename):
567 """Read a file and close it. Returns the file source."""
569 """Read a file and close it. Returns the file source."""
568 fobj = open(filename,'r');
570 fobj = open(filename,'r');
569 source = fobj.read();
571 source = fobj.read();
570 fobj.close()
572 fobj.close()
571 return source
573 return source
572
574
573 def file_readlines(filename):
575 def file_readlines(filename):
574 """Read a file and close it. Returns the file source using readlines()."""
576 """Read a file and close it. Returns the file source using readlines()."""
575 fobj = open(filename,'r');
577 fobj = open(filename,'r');
576 lines = fobj.readlines();
578 lines = fobj.readlines();
577 fobj.close()
579 fobj.close()
578 return lines
580 return lines
579
581
580 #----------------------------------------------------------------------------
582 #----------------------------------------------------------------------------
581 def target_outdated(target,deps):
583 def target_outdated(target,deps):
582 """Determine whether a target is out of date.
584 """Determine whether a target is out of date.
583
585
584 target_outdated(target,deps) -> 1/0
586 target_outdated(target,deps) -> 1/0
585
587
586 deps: list of filenames which MUST exist.
588 deps: list of filenames which MUST exist.
587 target: single filename which may or may not exist.
589 target: single filename which may or may not exist.
588
590
589 If target doesn't exist or is older than any file listed in deps, return
591 If target doesn't exist or is older than any file listed in deps, return
590 true, otherwise return false.
592 true, otherwise return false.
591 """
593 """
592 try:
594 try:
593 target_time = os.path.getmtime(target)
595 target_time = os.path.getmtime(target)
594 except os.error:
596 except os.error:
595 return 1
597 return 1
596 for dep in deps:
598 for dep in deps:
597 dep_time = os.path.getmtime(dep)
599 dep_time = os.path.getmtime(dep)
598 if dep_time > target_time:
600 if dep_time > target_time:
599 #print "For target",target,"Dep failed:",dep # dbg
601 #print "For target",target,"Dep failed:",dep # dbg
600 #print "times (dep,tar):",dep_time,target_time # dbg
602 #print "times (dep,tar):",dep_time,target_time # dbg
601 return 1
603 return 1
602 return 0
604 return 0
603
605
604 #-----------------------------------------------------------------------------
606 #-----------------------------------------------------------------------------
605 def target_update(target,deps,cmd):
607 def target_update(target,deps,cmd):
606 """Update a target with a given command given a list of dependencies.
608 """Update a target with a given command given a list of dependencies.
607
609
608 target_update(target,deps,cmd) -> runs cmd if target is outdated.
610 target_update(target,deps,cmd) -> runs cmd if target is outdated.
609
611
610 This is just a wrapper around target_outdated() which calls the given
612 This is just a wrapper around target_outdated() which calls the given
611 command if target is outdated."""
613 command if target is outdated."""
612
614
613 if target_outdated(target,deps):
615 if target_outdated(target,deps):
614 xsys(cmd)
616 xsys(cmd)
615
617
616 #----------------------------------------------------------------------------
618 #----------------------------------------------------------------------------
617 def unquote_ends(istr):
619 def unquote_ends(istr):
618 """Remove a single pair of quotes from the endpoints of a string."""
620 """Remove a single pair of quotes from the endpoints of a string."""
619
621
620 if not istr:
622 if not istr:
621 return istr
623 return istr
622 if (istr[0]=="'" and istr[-1]=="'") or \
624 if (istr[0]=="'" and istr[-1]=="'") or \
623 (istr[0]=='"' and istr[-1]=='"'):
625 (istr[0]=='"' and istr[-1]=='"'):
624 return istr[1:-1]
626 return istr[1:-1]
625 else:
627 else:
626 return istr
628 return istr
627
629
628 #----------------------------------------------------------------------------
630 #----------------------------------------------------------------------------
629 def process_cmdline(argv,names=[],defaults={},usage=''):
631 def process_cmdline(argv,names=[],defaults={},usage=''):
630 """ Process command-line options and arguments.
632 """ Process command-line options and arguments.
631
633
632 Arguments:
634 Arguments:
633
635
634 - argv: list of arguments, typically sys.argv.
636 - argv: list of arguments, typically sys.argv.
635
637
636 - names: list of option names. See DPyGetOpt docs for details on options
638 - names: list of option names. See DPyGetOpt docs for details on options
637 syntax.
639 syntax.
638
640
639 - defaults: dict of default values.
641 - defaults: dict of default values.
640
642
641 - usage: optional usage notice to print if a wrong argument is passed.
643 - usage: optional usage notice to print if a wrong argument is passed.
642
644
643 Return a dict of options and a list of free arguments."""
645 Return a dict of options and a list of free arguments."""
644
646
645 getopt = DPyGetOpt.DPyGetOpt()
647 getopt = DPyGetOpt.DPyGetOpt()
646 getopt.setIgnoreCase(0)
648 getopt.setIgnoreCase(0)
647 getopt.parseConfiguration(names)
649 getopt.parseConfiguration(names)
648
650
649 try:
651 try:
650 getopt.processArguments(argv)
652 getopt.processArguments(argv)
651 except DPyGetOpt.ArgumentError, exc:
653 except DPyGetOpt.ArgumentError, exc:
652 print usage
654 print usage
653 warn('"%s"' % exc,level=4)
655 warn('"%s"' % exc,level=4)
654
656
655 defaults.update(getopt.optionValues)
657 defaults.update(getopt.optionValues)
656 args = getopt.freeValues
658 args = getopt.freeValues
657
659
658 return defaults,args
660 return defaults,args
659
661
660 #----------------------------------------------------------------------------
662 #----------------------------------------------------------------------------
661 def optstr2types(ostr):
663 def optstr2types(ostr):
662 """Convert a string of option names to a dict of type mappings.
664 """Convert a string of option names to a dict of type mappings.
663
665
664 optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'}
666 optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'}
665
667
666 This is used to get the types of all the options in a string formatted
668 This is used to get the types of all the options in a string formatted
667 with the conventions of DPyGetOpt. The 'type' None is used for options
669 with the conventions of DPyGetOpt. The 'type' None is used for options
668 which are strings (they need no further conversion). This function's main
670 which are strings (they need no further conversion). This function's main
669 use is to get a typemap for use with read_dict().
671 use is to get a typemap for use with read_dict().
670 """
672 """
671
673
672 typeconv = {None:'',int:'',float:''}
674 typeconv = {None:'',int:'',float:''}
673 typemap = {'s':None,'i':int,'f':float}
675 typemap = {'s':None,'i':int,'f':float}
674 opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)')
676 opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)')
675
677
676 for w in ostr.split():
678 for w in ostr.split():
677 oname,alias,otype = opt_re.match(w).groups()
679 oname,alias,otype = opt_re.match(w).groups()
678 if otype == '' or alias == '!': # simple switches are integers too
680 if otype == '' or alias == '!': # simple switches are integers too
679 otype = 'i'
681 otype = 'i'
680 typeconv[typemap[otype]] += oname + ' '
682 typeconv[typemap[otype]] += oname + ' '
681 return typeconv
683 return typeconv
682
684
683 #----------------------------------------------------------------------------
685 #----------------------------------------------------------------------------
684 def read_dict(filename,type_conv=None,**opt):
686 def read_dict(filename,type_conv=None,**opt):
685
687
686 """Read a dictionary of key=value pairs from an input file, optionally
688 """Read a dictionary of key=value pairs from an input file, optionally
687 performing conversions on the resulting values.
689 performing conversions on the resulting values.
688
690
689 read_dict(filename,type_conv,**opt) -> dict
691 read_dict(filename,type_conv,**opt) -> dict
690
692
691 Only one value per line is accepted, the format should be
693 Only one value per line is accepted, the format should be
692 # optional comments are ignored
694 # optional comments are ignored
693 key value\n
695 key value\n
694
696
695 Args:
697 Args:
696
698
697 - type_conv: A dictionary specifying which keys need to be converted to
699 - type_conv: A dictionary specifying which keys need to be converted to
698 which types. By default all keys are read as strings. This dictionary
700 which types. By default all keys are read as strings. This dictionary
699 should have as its keys valid conversion functions for strings
701 should have as its keys valid conversion functions for strings
700 (int,long,float,complex, or your own). The value for each key
702 (int,long,float,complex, or your own). The value for each key
701 (converter) should be a whitespace separated string containing the names
703 (converter) should be a whitespace separated string containing the names
702 of all the entries in the file to be converted using that function. For
704 of all the entries in the file to be converted using that function. For
703 keys to be left alone, use None as the conversion function (only needed
705 keys to be left alone, use None as the conversion function (only needed
704 with purge=1, see below).
706 with purge=1, see below).
705
707
706 - opt: dictionary with extra options as below (default in parens)
708 - opt: dictionary with extra options as below (default in parens)
707
709
708 purge(0): if set to 1, all keys *not* listed in type_conv are purged out
710 purge(0): if set to 1, all keys *not* listed in type_conv are purged out
709 of the dictionary to be returned. If purge is going to be used, the
711 of the dictionary to be returned. If purge is going to be used, the
710 set of keys to be left as strings also has to be explicitly specified
712 set of keys to be left as strings also has to be explicitly specified
711 using the (non-existent) conversion function None.
713 using the (non-existent) conversion function None.
712
714
713 fs(None): field separator. This is the key/value separator to be used
715 fs(None): field separator. This is the key/value separator to be used
714 when parsing the file. The None default means any whitespace [behavior
716 when parsing the file. The None default means any whitespace [behavior
715 of string.split()].
717 of string.split()].
716
718
717 strip(0): if 1, strip string values of leading/trailinig whitespace.
719 strip(0): if 1, strip string values of leading/trailinig whitespace.
718
720
719 warn(1): warning level if requested keys are not found in file.
721 warn(1): warning level if requested keys are not found in file.
720 - 0: silently ignore.
722 - 0: silently ignore.
721 - 1: inform but proceed.
723 - 1: inform but proceed.
722 - 2: raise KeyError exception.
724 - 2: raise KeyError exception.
723
725
724 no_empty(0): if 1, remove keys with whitespace strings as a value.
726 no_empty(0): if 1, remove keys with whitespace strings as a value.
725
727
726 unique([]): list of keys (or space separated string) which can't be
728 unique([]): list of keys (or space separated string) which can't be
727 repeated. If one such key is found in the file, each new instance
729 repeated. If one such key is found in the file, each new instance
728 overwrites the previous one. For keys not listed here, the behavior is
730 overwrites the previous one. For keys not listed here, the behavior is
729 to make a list of all appearances.
731 to make a list of all appearances.
730
732
731 Example:
733 Example:
732 If the input file test.ini has:
734 If the input file test.ini has:
733 i 3
735 i 3
734 x 4.5
736 x 4.5
735 y 5.5
737 y 5.5
736 s hi ho
738 s hi ho
737 Then:
739 Then:
738
740
739 >>> type_conv={int:'i',float:'x',None:'s'}
741 >>> type_conv={int:'i',float:'x',None:'s'}
740 >>> read_dict('test.ini')
742 >>> read_dict('test.ini')
741 {'i': '3', 's': 'hi ho', 'x': '4.5', 'y': '5.5'}
743 {'i': '3', 's': 'hi ho', 'x': '4.5', 'y': '5.5'}
742 >>> read_dict('test.ini',type_conv)
744 >>> read_dict('test.ini',type_conv)
743 {'i': 3, 's': 'hi ho', 'x': 4.5, 'y': '5.5'}
745 {'i': 3, 's': 'hi ho', 'x': 4.5, 'y': '5.5'}
744 >>> read_dict('test.ini',type_conv,purge=1)
746 >>> read_dict('test.ini',type_conv,purge=1)
745 {'i': 3, 's': 'hi ho', 'x': 4.5}
747 {'i': 3, 's': 'hi ho', 'x': 4.5}
746 """
748 """
747
749
748 # starting config
750 # starting config
749 opt.setdefault('purge',0)
751 opt.setdefault('purge',0)
750 opt.setdefault('fs',None) # field sep defaults to any whitespace
752 opt.setdefault('fs',None) # field sep defaults to any whitespace
751 opt.setdefault('strip',0)
753 opt.setdefault('strip',0)
752 opt.setdefault('warn',1)
754 opt.setdefault('warn',1)
753 opt.setdefault('no_empty',0)
755 opt.setdefault('no_empty',0)
754 opt.setdefault('unique','')
756 opt.setdefault('unique','')
755 if type(opt['unique']) in StringTypes:
757 if type(opt['unique']) in StringTypes:
756 unique_keys = qw(opt['unique'])
758 unique_keys = qw(opt['unique'])
757 elif type(opt['unique']) in (types.TupleType,types.ListType):
759 elif type(opt['unique']) in (types.TupleType,types.ListType):
758 unique_keys = opt['unique']
760 unique_keys = opt['unique']
759 else:
761 else:
760 raise ValueError, 'Unique keys must be given as a string, List or Tuple'
762 raise ValueError, 'Unique keys must be given as a string, List or Tuple'
761
763
762 dict = {}
764 dict = {}
763 # first read in table of values as strings
765 # first read in table of values as strings
764 file = open(filename,'r')
766 file = open(filename,'r')
765 for line in file.readlines():
767 for line in file.readlines():
766 line = line.strip()
768 line = line.strip()
767 if len(line) and line[0]=='#': continue
769 if len(line) and line[0]=='#': continue
768 if len(line)>0:
770 if len(line)>0:
769 lsplit = line.split(opt['fs'],1)
771 lsplit = line.split(opt['fs'],1)
770 try:
772 try:
771 key,val = lsplit
773 key,val = lsplit
772 except ValueError:
774 except ValueError:
773 key,val = lsplit[0],''
775 key,val = lsplit[0],''
774 key = key.strip()
776 key = key.strip()
775 if opt['strip']: val = val.strip()
777 if opt['strip']: val = val.strip()
776 if val == "''" or val == '""': val = ''
778 if val == "''" or val == '""': val = ''
777 if opt['no_empty'] and (val=='' or val.isspace()):
779 if opt['no_empty'] and (val=='' or val.isspace()):
778 continue
780 continue
779 # if a key is found more than once in the file, build a list
781 # if a key is found more than once in the file, build a list
780 # unless it's in the 'unique' list. In that case, last found in file
782 # unless it's in the 'unique' list. In that case, last found in file
781 # takes precedence. User beware.
783 # takes precedence. User beware.
782 try:
784 try:
783 if dict[key] and key in unique_keys:
785 if dict[key] and key in unique_keys:
784 dict[key] = val
786 dict[key] = val
785 elif type(dict[key]) is types.ListType:
787 elif type(dict[key]) is types.ListType:
786 dict[key].append(val)
788 dict[key].append(val)
787 else:
789 else:
788 dict[key] = [dict[key],val]
790 dict[key] = [dict[key],val]
789 except KeyError:
791 except KeyError:
790 dict[key] = val
792 dict[key] = val
791 # purge if requested
793 # purge if requested
792 if opt['purge']:
794 if opt['purge']:
793 accepted_keys = qwflat(type_conv.values())
795 accepted_keys = qwflat(type_conv.values())
794 for key in dict.keys():
796 for key in dict.keys():
795 if key in accepted_keys: continue
797 if key in accepted_keys: continue
796 del(dict[key])
798 del(dict[key])
797 # now convert if requested
799 # now convert if requested
798 if type_conv==None: return dict
800 if type_conv==None: return dict
799 conversions = type_conv.keys()
801 conversions = type_conv.keys()
800 try: conversions.remove(None)
802 try: conversions.remove(None)
801 except: pass
803 except: pass
802 for convert in conversions:
804 for convert in conversions:
803 for val in qw(type_conv[convert]):
805 for val in qw(type_conv[convert]):
804 try:
806 try:
805 dict[val] = convert(dict[val])
807 dict[val] = convert(dict[val])
806 except KeyError,e:
808 except KeyError,e:
807 if opt['warn'] == 0:
809 if opt['warn'] == 0:
808 pass
810 pass
809 elif opt['warn'] == 1:
811 elif opt['warn'] == 1:
810 print >>sys.stderr, 'Warning: key',val,\
812 print >>sys.stderr, 'Warning: key',val,\
811 'not found in file',filename
813 'not found in file',filename
812 elif opt['warn'] == 2:
814 elif opt['warn'] == 2:
813 raise KeyError,e
815 raise KeyError,e
814 else:
816 else:
815 raise ValueError,'Warning level must be 0,1 or 2'
817 raise ValueError,'Warning level must be 0,1 or 2'
816
818
817 return dict
819 return dict
818
820
819 #----------------------------------------------------------------------------
821 #----------------------------------------------------------------------------
820 def flag_calls(func):
822 def flag_calls(func):
821 """Wrap a function to detect and flag when it gets called.
823 """Wrap a function to detect and flag when it gets called.
822
824
823 This is a decorator which takes a function and wraps it in a function with
825 This is a decorator which takes a function and wraps it in a function with
824 a 'called' attribute. wrapper.called is initialized to False.
826 a 'called' attribute. wrapper.called is initialized to False.
825
827
826 The wrapper.called attribute is set to False right before each call to the
828 The wrapper.called attribute is set to False right before each call to the
827 wrapped function, so if the call fails it remains False. After the call
829 wrapped function, so if the call fails it remains False. After the call
828 completes, wrapper.called is set to True and the output is returned.
830 completes, wrapper.called is set to True and the output is returned.
829
831
830 Testing for truth in wrapper.called allows you to determine if a call to
832 Testing for truth in wrapper.called allows you to determine if a call to
831 func() was attempted and succeeded."""
833 func() was attempted and succeeded."""
832
834
833 def wrapper(*args,**kw):
835 def wrapper(*args,**kw):
834 wrapper.called = False
836 wrapper.called = False
835 out = func(*args,**kw)
837 out = func(*args,**kw)
836 wrapper.called = True
838 wrapper.called = True
837 return out
839 return out
838
840
839 wrapper.called = False
841 wrapper.called = False
840 wrapper.__doc__ = func.__doc__
842 wrapper.__doc__ = func.__doc__
841 return wrapper
843 return wrapper
842
844
843 #----------------------------------------------------------------------------
845 #----------------------------------------------------------------------------
844 def dhook_wrap(func,*a,**k):
846 def dhook_wrap(func,*a,**k):
845 """Wrap a function call in a sys.displayhook controller.
847 """Wrap a function call in a sys.displayhook controller.
846
848
847 Returns a wrapper around func which calls func, with all its arguments and
849 Returns a wrapper around func which calls func, with all its arguments and
848 keywords unmodified, using the default sys.displayhook. Since IPython
850 keywords unmodified, using the default sys.displayhook. Since IPython
849 modifies sys.displayhook, it breaks the behavior of certain systems that
851 modifies sys.displayhook, it breaks the behavior of certain systems that
850 rely on the default behavior, notably doctest.
852 rely on the default behavior, notably doctest.
851 """
853 """
852
854
853 def f(*a,**k):
855 def f(*a,**k):
854
856
855 dhook_s = sys.displayhook
857 dhook_s = sys.displayhook
856 sys.displayhook = sys.__displayhook__
858 sys.displayhook = sys.__displayhook__
857 try:
859 try:
858 out = func(*a,**k)
860 out = func(*a,**k)
859 finally:
861 finally:
860 sys.displayhook = dhook_s
862 sys.displayhook = dhook_s
861
863
862 return out
864 return out
863
865
864 f.__doc__ = func.__doc__
866 f.__doc__ = func.__doc__
865 return f
867 return f
866
868
867 #----------------------------------------------------------------------------
869 #----------------------------------------------------------------------------
868 def doctest_reload():
870 def doctest_reload():
869 """Properly reload doctest to reuse it interactively.
871 """Properly reload doctest to reuse it interactively.
870
872
871 This routine:
873 This routine:
872
874
873 - reloads doctest
875 - reloads doctest
874
876
875 - resets its global 'master' attribute to None, so that multiple uses of
877 - resets its global 'master' attribute to None, so that multiple uses of
876 the module interactively don't produce cumulative reports.
878 the module interactively don't produce cumulative reports.
877
879
878 - Monkeypatches its core test runner method to protect it from IPython's
880 - Monkeypatches its core test runner method to protect it from IPython's
879 modified displayhook. Doctest expects the default displayhook behavior
881 modified displayhook. Doctest expects the default displayhook behavior
880 deep down, so our modification breaks it completely. For this reason, a
882 deep down, so our modification breaks it completely. For this reason, a
881 hard monkeypatch seems like a reasonable solution rather than asking
883 hard monkeypatch seems like a reasonable solution rather than asking
882 users to manually use a different doctest runner when under IPython."""
884 users to manually use a different doctest runner when under IPython."""
883
885
884 import doctest
886 import doctest
885 reload(doctest)
887 reload(doctest)
886 doctest.master=None
888 doctest.master=None
887
889
888 try:
890 try:
889 doctest.DocTestRunner
891 doctest.DocTestRunner
890 except AttributeError:
892 except AttributeError:
891 # This is only for python 2.3 compatibility, remove once we move to
893 # This is only for python 2.3 compatibility, remove once we move to
892 # 2.4 only.
894 # 2.4 only.
893 pass
895 pass
894 else:
896 else:
895 doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run)
897 doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run)
896
898
897 #----------------------------------------------------------------------------
899 #----------------------------------------------------------------------------
898 class HomeDirError(Error):
900 class HomeDirError(Error):
899 pass
901 pass
900
902
901 def get_home_dir():
903 def get_home_dir():
902 """Return the closest possible equivalent to a 'home' directory.
904 """Return the closest possible equivalent to a 'home' directory.
903
905
904 We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH.
906 We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH.
905
907
906 Currently only Posix and NT are implemented, a HomeDirError exception is
908 Currently only Posix and NT are implemented, a HomeDirError exception is
907 raised for all other OSes. """
909 raised for all other OSes. """
908
910
909 isdir = os.path.isdir
911 isdir = os.path.isdir
910 env = os.environ
912 env = os.environ
911
913
912 # first, check py2exe distribution root directory for _ipython.
914 # first, check py2exe distribution root directory for _ipython.
913 # This overrides all. Normally does not exist.
915 # This overrides all. Normally does not exist.
914
916
915 if '\\library.zip\\' in IPython.__file__.lower():
917 if '\\library.zip\\' in IPython.__file__.lower():
916 root, rest = IPython.__file__.lower().split('library.zip')
918 root, rest = IPython.__file__.lower().split('library.zip')
917 if isdir(root + '_ipython'):
919 if isdir(root + '_ipython'):
918 os.environ["IPYKITROOT"] = root.rstrip('\\')
920 os.environ["IPYKITROOT"] = root.rstrip('\\')
919 return root
921 return root
920
922
921 try:
923 try:
922 homedir = env['HOME']
924 homedir = env['HOME']
923 if not isdir(homedir):
925 if not isdir(homedir):
924 # in case a user stuck some string which does NOT resolve to a
926 # in case a user stuck some string which does NOT resolve to a
925 # valid path, it's as good as if we hadn't foud it
927 # valid path, it's as good as if we hadn't foud it
926 raise KeyError
928 raise KeyError
927 return homedir
929 return homedir
928 except KeyError:
930 except KeyError:
929 if os.name == 'posix':
931 if os.name == 'posix':
930 raise HomeDirError,'undefined $HOME, IPython can not proceed.'
932 raise HomeDirError,'undefined $HOME, IPython can not proceed.'
931 elif os.name == 'nt':
933 elif os.name == 'nt':
932 # For some strange reason, win9x returns 'nt' for os.name.
934 # For some strange reason, win9x returns 'nt' for os.name.
933 try:
935 try:
934 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
936 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
935 if not isdir(homedir):
937 if not isdir(homedir):
936 homedir = os.path.join(env['USERPROFILE'])
938 homedir = os.path.join(env['USERPROFILE'])
937 if not isdir(homedir):
939 if not isdir(homedir):
938 raise HomeDirError
940 raise HomeDirError
939 return homedir
941 return homedir
940 except:
942 except:
941 try:
943 try:
942 # Use the registry to get the 'My Documents' folder.
944 # Use the registry to get the 'My Documents' folder.
943 import _winreg as wreg
945 import _winreg as wreg
944 key = wreg.OpenKey(wreg.HKEY_CURRENT_USER,
946 key = wreg.OpenKey(wreg.HKEY_CURRENT_USER,
945 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
947 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
946 homedir = wreg.QueryValueEx(key,'Personal')[0]
948 homedir = wreg.QueryValueEx(key,'Personal')[0]
947 key.Close()
949 key.Close()
948 if not isdir(homedir):
950 if not isdir(homedir):
949 e = ('Invalid "Personal" folder registry key '
951 e = ('Invalid "Personal" folder registry key '
950 'typically "My Documents".\n'
952 'typically "My Documents".\n'
951 'Value: %s\n'
953 'Value: %s\n'
952 'This is not a valid directory on your system.' %
954 'This is not a valid directory on your system.' %
953 homedir)
955 homedir)
954 raise HomeDirError(e)
956 raise HomeDirError(e)
955 return homedir
957 return homedir
956 except HomeDirError:
958 except HomeDirError:
957 raise
959 raise
958 except:
960 except:
959 return 'C:\\'
961 return 'C:\\'
960 elif os.name == 'dos':
962 elif os.name == 'dos':
961 # Desperate, may do absurd things in classic MacOS. May work under DOS.
963 # Desperate, may do absurd things in classic MacOS. May work under DOS.
962 return 'C:\\'
964 return 'C:\\'
963 else:
965 else:
964 raise HomeDirError,'support for your operating system not implemented.'
966 raise HomeDirError,'support for your operating system not implemented.'
965
967
966 #****************************************************************************
968 #****************************************************************************
967 # strings and text
969 # strings and text
968
970
969 class LSString(str):
971 class LSString(str):
970 """String derivative with a special access attributes.
972 """String derivative with a special access attributes.
971
973
972 These are normal strings, but with the special attributes:
974 These are normal strings, but with the special attributes:
973
975
974 .l (or .list) : value as list (split on newlines).
976 .l (or .list) : value as list (split on newlines).
975 .n (or .nlstr): original value (the string itself).
977 .n (or .nlstr): original value (the string itself).
976 .s (or .spstr): value as whitespace-separated string.
978 .s (or .spstr): value as whitespace-separated string.
977 .p (or .paths): list of path objects
979 .p (or .paths): list of path objects
978
980
979 Any values which require transformations are computed only once and
981 Any values which require transformations are computed only once and
980 cached.
982 cached.
981
983
982 Such strings are very useful to efficiently interact with the shell, which
984 Such strings are very useful to efficiently interact with the shell, which
983 typically only understands whitespace-separated options for commands."""
985 typically only understands whitespace-separated options for commands."""
984
986
985 def get_list(self):
987 def get_list(self):
986 try:
988 try:
987 return self.__list
989 return self.__list
988 except AttributeError:
990 except AttributeError:
989 self.__list = self.split('\n')
991 self.__list = self.split('\n')
990 return self.__list
992 return self.__list
991
993
992 l = list = property(get_list)
994 l = list = property(get_list)
993
995
994 def get_spstr(self):
996 def get_spstr(self):
995 try:
997 try:
996 return self.__spstr
998 return self.__spstr
997 except AttributeError:
999 except AttributeError:
998 self.__spstr = self.replace('\n',' ')
1000 self.__spstr = self.replace('\n',' ')
999 return self.__spstr
1001 return self.__spstr
1000
1002
1001 s = spstr = property(get_spstr)
1003 s = spstr = property(get_spstr)
1002
1004
1003 def get_nlstr(self):
1005 def get_nlstr(self):
1004 return self
1006 return self
1005
1007
1006 n = nlstr = property(get_nlstr)
1008 n = nlstr = property(get_nlstr)
1007
1009
1008 def get_paths(self):
1010 def get_paths(self):
1009 try:
1011 try:
1010 return self.__paths
1012 return self.__paths
1011 except AttributeError:
1013 except AttributeError:
1012 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
1014 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
1013 return self.__paths
1015 return self.__paths
1014
1016
1015 p = paths = property(get_paths)
1017 p = paths = property(get_paths)
1016
1018
1017 def print_lsstring(arg):
1019 def print_lsstring(arg):
1018 """ Prettier (non-repr-like) and more informative printer for LSString """
1020 """ Prettier (non-repr-like) and more informative printer for LSString """
1019 print "LSString (.p, .n, .l, .s available). Value:"
1021 print "LSString (.p, .n, .l, .s available). Value:"
1020 print arg
1022 print arg
1021
1023
1022 print_lsstring = result_display.when_type(LSString)(print_lsstring)
1024 print_lsstring = result_display.when_type(LSString)(print_lsstring)
1023
1025
1024 #----------------------------------------------------------------------------
1026 #----------------------------------------------------------------------------
1025 class SList(list):
1027 class SList(list):
1026 """List derivative with a special access attributes.
1028 """List derivative with a special access attributes.
1027
1029
1028 These are normal lists, but with the special attributes:
1030 These are normal lists, but with the special attributes:
1029
1031
1030 .l (or .list) : value as list (the list itself).
1032 .l (or .list) : value as list (the list itself).
1031 .n (or .nlstr): value as a string, joined on newlines.
1033 .n (or .nlstr): value as a string, joined on newlines.
1032 .s (or .spstr): value as a string, joined on spaces.
1034 .s (or .spstr): value as a string, joined on spaces.
1033 .p (or .paths): list of path objects
1035 .p (or .paths): list of path objects
1034
1036
1035 Any values which require transformations are computed only once and
1037 Any values which require transformations are computed only once and
1036 cached."""
1038 cached."""
1037
1039
1038 def get_list(self):
1040 def get_list(self):
1039 return self
1041 return self
1040
1042
1041 l = list = property(get_list)
1043 l = list = property(get_list)
1042
1044
1043 def get_spstr(self):
1045 def get_spstr(self):
1044 try:
1046 try:
1045 return self.__spstr
1047 return self.__spstr
1046 except AttributeError:
1048 except AttributeError:
1047 self.__spstr = ' '.join(self)
1049 self.__spstr = ' '.join(self)
1048 return self.__spstr
1050 return self.__spstr
1049
1051
1050 s = spstr = property(get_spstr)
1052 s = spstr = property(get_spstr)
1051
1053
1052 def get_nlstr(self):
1054 def get_nlstr(self):
1053 try:
1055 try:
1054 return self.__nlstr
1056 return self.__nlstr
1055 except AttributeError:
1057 except AttributeError:
1056 self.__nlstr = '\n'.join(self)
1058 self.__nlstr = '\n'.join(self)
1057 return self.__nlstr
1059 return self.__nlstr
1058
1060
1059 n = nlstr = property(get_nlstr)
1061 n = nlstr = property(get_nlstr)
1060
1062
1061 def get_paths(self):
1063 def get_paths(self):
1062 try:
1064 try:
1063 return self.__paths
1065 return self.__paths
1064 except AttributeError:
1066 except AttributeError:
1065 self.__paths = [path(p) for p in self if os.path.exists(p)]
1067 self.__paths = [path(p) for p in self if os.path.exists(p)]
1066 return self.__paths
1068 return self.__paths
1067
1069
1068 p = paths = property(get_paths)
1070 p = paths = property(get_paths)
1069
1071
1070 def grep(self, pattern, prune = False, field = None):
1072 def grep(self, pattern, prune = False, field = None):
1071 """ Return all strings matching 'pattern' (a regex or callable)
1073 """ Return all strings matching 'pattern' (a regex or callable)
1072
1074
1073 This is case-insensitive. If prune is true, return all items
1075 This is case-insensitive. If prune is true, return all items
1074 NOT matching the pattern.
1076 NOT matching the pattern.
1075
1077
1076 If field is specified, the match must occur in the specified
1078 If field is specified, the match must occur in the specified
1077 whitespace-separated field.
1079 whitespace-separated field.
1078
1080
1079 Examples::
1081 Examples::
1080
1082
1081 a.grep( lambda x: x.startswith('C') )
1083 a.grep( lambda x: x.startswith('C') )
1082 a.grep('Cha.*log', prune=1)
1084 a.grep('Cha.*log', prune=1)
1083 a.grep('chm', field=-1)
1085 a.grep('chm', field=-1)
1084 """
1086 """
1085
1087
1086 def match_target(s):
1088 def match_target(s):
1087 if field is None:
1089 if field is None:
1088 return s
1090 return s
1089 parts = s.split()
1091 parts = s.split()
1090 try:
1092 try:
1091 tgt = parts[field]
1093 tgt = parts[field]
1092 return tgt
1094 return tgt
1093 except IndexError:
1095 except IndexError:
1094 return ""
1096 return ""
1095
1097
1096 if isinstance(pattern, basestring):
1098 if isinstance(pattern, basestring):
1097 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
1099 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
1098 else:
1100 else:
1099 pred = pattern
1101 pred = pattern
1100 if not prune:
1102 if not prune:
1101 return SList([el for el in self if pred(match_target(el))])
1103 return SList([el for el in self if pred(match_target(el))])
1102 else:
1104 else:
1103 return SList([el for el in self if not pred(match_target(el))])
1105 return SList([el for el in self if not pred(match_target(el))])
1104 def fields(self, *fields):
1106 def fields(self, *fields):
1105 """ Collect whitespace-separated fields from string list
1107 """ Collect whitespace-separated fields from string list
1106
1108
1107 Allows quick awk-like usage of string lists.
1109 Allows quick awk-like usage of string lists.
1108
1110
1109 Example data (in var a, created by 'a = !ls -l')::
1111 Example data (in var a, created by 'a = !ls -l')::
1110 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
1112 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
1111 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
1113 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
1112
1114
1113 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
1115 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
1114 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
1116 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
1115 (note the joining by space).
1117 (note the joining by space).
1116 a.fields(-1) is ['ChangeLog', 'IPython']
1118 a.fields(-1) is ['ChangeLog', 'IPython']
1117
1119
1118 IndexErrors are ignored.
1120 IndexErrors are ignored.
1119
1121
1120 Without args, fields() just split()'s the strings.
1122 Without args, fields() just split()'s the strings.
1121 """
1123 """
1122 if len(fields) == 0:
1124 if len(fields) == 0:
1123 return [el.split() for el in self]
1125 return [el.split() for el in self]
1124
1126
1125 res = SList()
1127 res = SList()
1126 for el in [f.split() for f in self]:
1128 for el in [f.split() for f in self]:
1127 lineparts = []
1129 lineparts = []
1128
1130
1129 for fd in fields:
1131 for fd in fields:
1130 try:
1132 try:
1131 lineparts.append(el[fd])
1133 lineparts.append(el[fd])
1132 except IndexError:
1134 except IndexError:
1133 pass
1135 pass
1134 if lineparts:
1136 if lineparts:
1135 res.append(" ".join(lineparts))
1137 res.append(" ".join(lineparts))
1136
1138
1137 return res
1139 return res
1138
1140
1139
1141
1140
1142
1141
1143
1142
1144
1143 def print_slist(arg):
1145 def print_slist(arg):
1144 """ Prettier (non-repr-like) and more informative printer for SList """
1146 """ Prettier (non-repr-like) and more informative printer for SList """
1145 print "SList (.p, .n, .l, .s, .grep(), .fields() available). Value:"
1147 print "SList (.p, .n, .l, .s, .grep(), .fields() available). Value:"
1146 nlprint(arg)
1148 nlprint(arg)
1147
1149
1148 print_slist = result_display.when_type(SList)(print_slist)
1150 print_slist = result_display.when_type(SList)(print_slist)
1149
1151
1150
1152
1151
1153
1152 #----------------------------------------------------------------------------
1154 #----------------------------------------------------------------------------
1153 def esc_quotes(strng):
1155 def esc_quotes(strng):
1154 """Return the input string with single and double quotes escaped out"""
1156 """Return the input string with single and double quotes escaped out"""
1155
1157
1156 return strng.replace('"','\\"').replace("'","\\'")
1158 return strng.replace('"','\\"').replace("'","\\'")
1157
1159
1158 #----------------------------------------------------------------------------
1160 #----------------------------------------------------------------------------
1159 def make_quoted_expr(s):
1161 def make_quoted_expr(s):
1160 """Return string s in appropriate quotes, using raw string if possible.
1162 """Return string s in appropriate quotes, using raw string if possible.
1161
1163
1162 Effectively this turns string: cd \ao\ao\
1164 Effectively this turns string: cd \ao\ao\
1163 to: r"cd \ao\ao\_"[:-1]
1165 to: r"cd \ao\ao\_"[:-1]
1164
1166
1165 Note the use of raw string and padding at the end to allow trailing backslash.
1167 Note the use of raw string and padding at the end to allow trailing backslash.
1166
1168
1167 """
1169 """
1168
1170
1169 tail = ''
1171 tail = ''
1170 tailpadding = ''
1172 tailpadding = ''
1171 raw = ''
1173 raw = ''
1172 if "\\" in s:
1174 if "\\" in s:
1173 raw = 'r'
1175 raw = 'r'
1174 if s.endswith('\\'):
1176 if s.endswith('\\'):
1175 tail = '[:-1]'
1177 tail = '[:-1]'
1176 tailpadding = '_'
1178 tailpadding = '_'
1177 if '"' not in s:
1179 if '"' not in s:
1178 quote = '"'
1180 quote = '"'
1179 elif "'" not in s:
1181 elif "'" not in s:
1180 quote = "'"
1182 quote = "'"
1181 elif '"""' not in s and not s.endswith('"'):
1183 elif '"""' not in s and not s.endswith('"'):
1182 quote = '"""'
1184 quote = '"""'
1183 elif "'''" not in s and not s.endswith("'"):
1185 elif "'''" not in s and not s.endswith("'"):
1184 quote = "'''"
1186 quote = "'''"
1185 else:
1187 else:
1186 # give up, backslash-escaped string will do
1188 # give up, backslash-escaped string will do
1187 return '"%s"' % esc_quotes(s)
1189 return '"%s"' % esc_quotes(s)
1188 res = raw + quote + s + tailpadding + quote + tail
1190 res = raw + quote + s + tailpadding + quote + tail
1189 return res
1191 return res
1190
1192
1191
1193
1192 #----------------------------------------------------------------------------
1194 #----------------------------------------------------------------------------
1193 def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'):
1195 def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'):
1194 """Take multiple lines of input.
1196 """Take multiple lines of input.
1195
1197
1196 A list with each line of input as a separate element is returned when a
1198 A list with each line of input as a separate element is returned when a
1197 termination string is entered (defaults to a single '.'). Input can also
1199 termination string is entered (defaults to a single '.'). Input can also
1198 terminate via EOF (^D in Unix, ^Z-RET in Windows).
1200 terminate via EOF (^D in Unix, ^Z-RET in Windows).
1199
1201
1200 Lines of input which end in \\ are joined into single entries (and a
1202 Lines of input which end in \\ are joined into single entries (and a
1201 secondary continuation prompt is issued as long as the user terminates
1203 secondary continuation prompt is issued as long as the user terminates
1202 lines with \\). This allows entering very long strings which are still
1204 lines with \\). This allows entering very long strings which are still
1203 meant to be treated as single entities.
1205 meant to be treated as single entities.
1204 """
1206 """
1205
1207
1206 try:
1208 try:
1207 if header:
1209 if header:
1208 header += '\n'
1210 header += '\n'
1209 lines = [raw_input(header + ps1)]
1211 lines = [raw_input(header + ps1)]
1210 except EOFError:
1212 except EOFError:
1211 return []
1213 return []
1212 terminate = [terminate_str]
1214 terminate = [terminate_str]
1213 try:
1215 try:
1214 while lines[-1:] != terminate:
1216 while lines[-1:] != terminate:
1215 new_line = raw_input(ps1)
1217 new_line = raw_input(ps1)
1216 while new_line.endswith('\\'):
1218 while new_line.endswith('\\'):
1217 new_line = new_line[:-1] + raw_input(ps2)
1219 new_line = new_line[:-1] + raw_input(ps2)
1218 lines.append(new_line)
1220 lines.append(new_line)
1219
1221
1220 return lines[:-1] # don't return the termination command
1222 return lines[:-1] # don't return the termination command
1221 except EOFError:
1223 except EOFError:
1222 print
1224 print
1223 return lines
1225 return lines
1224
1226
1225 #----------------------------------------------------------------------------
1227 #----------------------------------------------------------------------------
1226 def raw_input_ext(prompt='', ps2='... '):
1228 def raw_input_ext(prompt='', ps2='... '):
1227 """Similar to raw_input(), but accepts extended lines if input ends with \\."""
1229 """Similar to raw_input(), but accepts extended lines if input ends with \\."""
1228
1230
1229 line = raw_input(prompt)
1231 line = raw_input(prompt)
1230 while line.endswith('\\'):
1232 while line.endswith('\\'):
1231 line = line[:-1] + raw_input(ps2)
1233 line = line[:-1] + raw_input(ps2)
1232 return line
1234 return line
1233
1235
1234 #----------------------------------------------------------------------------
1236 #----------------------------------------------------------------------------
1235 def ask_yes_no(prompt,default=None):
1237 def ask_yes_no(prompt,default=None):
1236 """Asks a question and returns a boolean (y/n) answer.
1238 """Asks a question and returns a boolean (y/n) answer.
1237
1239
1238 If default is given (one of 'y','n'), it is used if the user input is
1240 If default is given (one of 'y','n'), it is used if the user input is
1239 empty. Otherwise the question is repeated until an answer is given.
1241 empty. Otherwise the question is repeated until an answer is given.
1240
1242
1241 An EOF is treated as the default answer. If there is no default, an
1243 An EOF is treated as the default answer. If there is no default, an
1242 exception is raised to prevent infinite loops.
1244 exception is raised to prevent infinite loops.
1243
1245
1244 Valid answers are: y/yes/n/no (match is not case sensitive)."""
1246 Valid answers are: y/yes/n/no (match is not case sensitive)."""
1245
1247
1246 answers = {'y':True,'n':False,'yes':True,'no':False}
1248 answers = {'y':True,'n':False,'yes':True,'no':False}
1247 ans = None
1249 ans = None
1248 while ans not in answers.keys():
1250 while ans not in answers.keys():
1249 try:
1251 try:
1250 ans = raw_input(prompt+' ').lower()
1252 ans = raw_input(prompt+' ').lower()
1251 if not ans: # response was an empty string
1253 if not ans: # response was an empty string
1252 ans = default
1254 ans = default
1253 except KeyboardInterrupt:
1255 except KeyboardInterrupt:
1254 pass
1256 pass
1255 except EOFError:
1257 except EOFError:
1256 if default in answers.keys():
1258 if default in answers.keys():
1257 ans = default
1259 ans = default
1258 print
1260 print
1259 else:
1261 else:
1260 raise
1262 raise
1261
1263
1262 return answers[ans]
1264 return answers[ans]
1263
1265
1264 #----------------------------------------------------------------------------
1266 #----------------------------------------------------------------------------
1265 def marquee(txt='',width=78,mark='*'):
1267 def marquee(txt='',width=78,mark='*'):
1266 """Return the input string centered in a 'marquee'."""
1268 """Return the input string centered in a 'marquee'."""
1267 if not txt:
1269 if not txt:
1268 return (mark*width)[:width]
1270 return (mark*width)[:width]
1269 nmark = (width-len(txt)-2)/len(mark)/2
1271 nmark = (width-len(txt)-2)/len(mark)/2
1270 if nmark < 0: nmark =0
1272 if nmark < 0: nmark =0
1271 marks = mark*nmark
1273 marks = mark*nmark
1272 return '%s %s %s' % (marks,txt,marks)
1274 return '%s %s %s' % (marks,txt,marks)
1273
1275
1274 #----------------------------------------------------------------------------
1276 #----------------------------------------------------------------------------
1275 class EvalDict:
1277 class EvalDict:
1276 """
1278 """
1277 Emulate a dict which evaluates its contents in the caller's frame.
1279 Emulate a dict which evaluates its contents in the caller's frame.
1278
1280
1279 Usage:
1281 Usage:
1280 >>>number = 19
1282 >>>number = 19
1281 >>>text = "python"
1283 >>>text = "python"
1282 >>>print "%(text.capitalize())s %(number/9.0).1f rules!" % EvalDict()
1284 >>>print "%(text.capitalize())s %(number/9.0).1f rules!" % EvalDict()
1283 """
1285 """
1284
1286
1285 # This version is due to sismex01@hebmex.com on c.l.py, and is basically a
1287 # This version is due to sismex01@hebmex.com on c.l.py, and is basically a
1286 # modified (shorter) version of:
1288 # modified (shorter) version of:
1287 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66018 by
1289 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66018 by
1288 # Skip Montanaro (skip@pobox.com).
1290 # Skip Montanaro (skip@pobox.com).
1289
1291
1290 def __getitem__(self, name):
1292 def __getitem__(self, name):
1291 frame = sys._getframe(1)
1293 frame = sys._getframe(1)
1292 return eval(name, frame.f_globals, frame.f_locals)
1294 return eval(name, frame.f_globals, frame.f_locals)
1293
1295
1294 EvalString = EvalDict # for backwards compatibility
1296 EvalString = EvalDict # for backwards compatibility
1295 #----------------------------------------------------------------------------
1297 #----------------------------------------------------------------------------
1296 def qw(words,flat=0,sep=None,maxsplit=-1):
1298 def qw(words,flat=0,sep=None,maxsplit=-1):
1297 """Similar to Perl's qw() operator, but with some more options.
1299 """Similar to Perl's qw() operator, but with some more options.
1298
1300
1299 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
1301 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
1300
1302
1301 words can also be a list itself, and with flat=1, the output will be
1303 words can also be a list itself, and with flat=1, the output will be
1302 recursively flattened. Examples:
1304 recursively flattened. Examples:
1303
1305
1304 >>> qw('1 2')
1306 >>> qw('1 2')
1305 ['1', '2']
1307 ['1', '2']
1306 >>> qw(['a b','1 2',['m n','p q']])
1308 >>> qw(['a b','1 2',['m n','p q']])
1307 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
1309 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
1308 >>> qw(['a b','1 2',['m n','p q']],flat=1)
1310 >>> qw(['a b','1 2',['m n','p q']],flat=1)
1309 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q'] """
1311 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q'] """
1310
1312
1311 if type(words) in StringTypes:
1313 if type(words) in StringTypes:
1312 return [word.strip() for word in words.split(sep,maxsplit)
1314 return [word.strip() for word in words.split(sep,maxsplit)
1313 if word and not word.isspace() ]
1315 if word and not word.isspace() ]
1314 if flat:
1316 if flat:
1315 return flatten(map(qw,words,[1]*len(words)))
1317 return flatten(map(qw,words,[1]*len(words)))
1316 return map(qw,words)
1318 return map(qw,words)
1317
1319
1318 #----------------------------------------------------------------------------
1320 #----------------------------------------------------------------------------
1319 def qwflat(words,sep=None,maxsplit=-1):
1321 def qwflat(words,sep=None,maxsplit=-1):
1320 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
1322 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
1321 return qw(words,1,sep,maxsplit)
1323 return qw(words,1,sep,maxsplit)
1322
1324
1323 #----------------------------------------------------------------------------
1325 #----------------------------------------------------------------------------
1324 def qw_lol(indata):
1326 def qw_lol(indata):
1325 """qw_lol('a b') -> [['a','b']],
1327 """qw_lol('a b') -> [['a','b']],
1326 otherwise it's just a call to qw().
1328 otherwise it's just a call to qw().
1327
1329
1328 We need this to make sure the modules_some keys *always* end up as a
1330 We need this to make sure the modules_some keys *always* end up as a
1329 list of lists."""
1331 list of lists."""
1330
1332
1331 if type(indata) in StringTypes:
1333 if type(indata) in StringTypes:
1332 return [qw(indata)]
1334 return [qw(indata)]
1333 else:
1335 else:
1334 return qw(indata)
1336 return qw(indata)
1335
1337
1336 #-----------------------------------------------------------------------------
1338 #-----------------------------------------------------------------------------
1337 def list_strings(arg):
1339 def list_strings(arg):
1338 """Always return a list of strings, given a string or list of strings
1340 """Always return a list of strings, given a string or list of strings
1339 as input."""
1341 as input."""
1340
1342
1341 if type(arg) in StringTypes: return [arg]
1343 if type(arg) in StringTypes: return [arg]
1342 else: return arg
1344 else: return arg
1343
1345
1344 #----------------------------------------------------------------------------
1346 #----------------------------------------------------------------------------
1345 def grep(pat,list,case=1):
1347 def grep(pat,list,case=1):
1346 """Simple minded grep-like function.
1348 """Simple minded grep-like function.
1347 grep(pat,list) returns occurrences of pat in list, None on failure.
1349 grep(pat,list) returns occurrences of pat in list, None on failure.
1348
1350
1349 It only does simple string matching, with no support for regexps. Use the
1351 It only does simple string matching, with no support for regexps. Use the
1350 option case=0 for case-insensitive matching."""
1352 option case=0 for case-insensitive matching."""
1351
1353
1352 # This is pretty crude. At least it should implement copying only references
1354 # This is pretty crude. At least it should implement copying only references
1353 # to the original data in case it's big. Now it copies the data for output.
1355 # to the original data in case it's big. Now it copies the data for output.
1354 out=[]
1356 out=[]
1355 if case:
1357 if case:
1356 for term in list:
1358 for term in list:
1357 if term.find(pat)>-1: out.append(term)
1359 if term.find(pat)>-1: out.append(term)
1358 else:
1360 else:
1359 lpat=pat.lower()
1361 lpat=pat.lower()
1360 for term in list:
1362 for term in list:
1361 if term.lower().find(lpat)>-1: out.append(term)
1363 if term.lower().find(lpat)>-1: out.append(term)
1362
1364
1363 if len(out): return out
1365 if len(out): return out
1364 else: return None
1366 else: return None
1365
1367
1366 #----------------------------------------------------------------------------
1368 #----------------------------------------------------------------------------
1367 def dgrep(pat,*opts):
1369 def dgrep(pat,*opts):
1368 """Return grep() on dir()+dir(__builtins__).
1370 """Return grep() on dir()+dir(__builtins__).
1369
1371
1370 A very common use of grep() when working interactively."""
1372 A very common use of grep() when working interactively."""
1371
1373
1372 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
1374 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
1373
1375
1374 #----------------------------------------------------------------------------
1376 #----------------------------------------------------------------------------
1375 def idgrep(pat):
1377 def idgrep(pat):
1376 """Case-insensitive dgrep()"""
1378 """Case-insensitive dgrep()"""
1377
1379
1378 return dgrep(pat,0)
1380 return dgrep(pat,0)
1379
1381
1380 #----------------------------------------------------------------------------
1382 #----------------------------------------------------------------------------
1381 def igrep(pat,list):
1383 def igrep(pat,list):
1382 """Synonym for case-insensitive grep."""
1384 """Synonym for case-insensitive grep."""
1383
1385
1384 return grep(pat,list,case=0)
1386 return grep(pat,list,case=0)
1385
1387
1386 #----------------------------------------------------------------------------
1388 #----------------------------------------------------------------------------
1387 def indent(str,nspaces=4,ntabs=0):
1389 def indent(str,nspaces=4,ntabs=0):
1388 """Indent a string a given number of spaces or tabstops.
1390 """Indent a string a given number of spaces or tabstops.
1389
1391
1390 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
1392 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
1391 """
1393 """
1392 if str is None:
1394 if str is None:
1393 return
1395 return
1394 ind = '\t'*ntabs+' '*nspaces
1396 ind = '\t'*ntabs+' '*nspaces
1395 outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind))
1397 outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind))
1396 if outstr.endswith(os.linesep+ind):
1398 if outstr.endswith(os.linesep+ind):
1397 return outstr[:-len(ind)]
1399 return outstr[:-len(ind)]
1398 else:
1400 else:
1399 return outstr
1401 return outstr
1400
1402
1401 #-----------------------------------------------------------------------------
1403 #-----------------------------------------------------------------------------
1402 def native_line_ends(filename,backup=1):
1404 def native_line_ends(filename,backup=1):
1403 """Convert (in-place) a file to line-ends native to the current OS.
1405 """Convert (in-place) a file to line-ends native to the current OS.
1404
1406
1405 If the optional backup argument is given as false, no backup of the
1407 If the optional backup argument is given as false, no backup of the
1406 original file is left. """
1408 original file is left. """
1407
1409
1408 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
1410 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
1409
1411
1410 bak_filename = filename + backup_suffixes[os.name]
1412 bak_filename = filename + backup_suffixes[os.name]
1411
1413
1412 original = open(filename).read()
1414 original = open(filename).read()
1413 shutil.copy2(filename,bak_filename)
1415 shutil.copy2(filename,bak_filename)
1414 try:
1416 try:
1415 new = open(filename,'wb')
1417 new = open(filename,'wb')
1416 new.write(os.linesep.join(original.splitlines()))
1418 new.write(os.linesep.join(original.splitlines()))
1417 new.write(os.linesep) # ALWAYS put an eol at the end of the file
1419 new.write(os.linesep) # ALWAYS put an eol at the end of the file
1418 new.close()
1420 new.close()
1419 except:
1421 except:
1420 os.rename(bak_filename,filename)
1422 os.rename(bak_filename,filename)
1421 if not backup:
1423 if not backup:
1422 try:
1424 try:
1423 os.remove(bak_filename)
1425 os.remove(bak_filename)
1424 except:
1426 except:
1425 pass
1427 pass
1426
1428
1427 #----------------------------------------------------------------------------
1429 #----------------------------------------------------------------------------
1428 def get_pager_cmd(pager_cmd = None):
1430 def get_pager_cmd(pager_cmd = None):
1429 """Return a pager command.
1431 """Return a pager command.
1430
1432
1431 Makes some attempts at finding an OS-correct one."""
1433 Makes some attempts at finding an OS-correct one."""
1432
1434
1433 if os.name == 'posix':
1435 if os.name == 'posix':
1434 default_pager_cmd = 'less -r' # -r for color control sequences
1436 default_pager_cmd = 'less -r' # -r for color control sequences
1435 elif os.name in ['nt','dos']:
1437 elif os.name in ['nt','dos']:
1436 default_pager_cmd = 'type'
1438 default_pager_cmd = 'type'
1437
1439
1438 if pager_cmd is None:
1440 if pager_cmd is None:
1439 try:
1441 try:
1440 pager_cmd = os.environ['PAGER']
1442 pager_cmd = os.environ['PAGER']
1441 except:
1443 except:
1442 pager_cmd = default_pager_cmd
1444 pager_cmd = default_pager_cmd
1443 return pager_cmd
1445 return pager_cmd
1444
1446
1445 #-----------------------------------------------------------------------------
1447 #-----------------------------------------------------------------------------
1446 def get_pager_start(pager,start):
1448 def get_pager_start(pager,start):
1447 """Return the string for paging files with an offset.
1449 """Return the string for paging files with an offset.
1448
1450
1449 This is the '+N' argument which less and more (under Unix) accept.
1451 This is the '+N' argument which less and more (under Unix) accept.
1450 """
1452 """
1451
1453
1452 if pager in ['less','more']:
1454 if pager in ['less','more']:
1453 if start:
1455 if start:
1454 start_string = '+' + str(start)
1456 start_string = '+' + str(start)
1455 else:
1457 else:
1456 start_string = ''
1458 start_string = ''
1457 else:
1459 else:
1458 start_string = ''
1460 start_string = ''
1459 return start_string
1461 return start_string
1460
1462
1461 #----------------------------------------------------------------------------
1463 #----------------------------------------------------------------------------
1462 # (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch()
1464 # (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch()
1463 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
1465 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
1464 import msvcrt
1466 import msvcrt
1465 def page_more():
1467 def page_more():
1466 """ Smart pausing between pages
1468 """ Smart pausing between pages
1467
1469
1468 @return: True if need print more lines, False if quit
1470 @return: True if need print more lines, False if quit
1469 """
1471 """
1470 Term.cout.write('---Return to continue, q to quit--- ')
1472 Term.cout.write('---Return to continue, q to quit--- ')
1471 ans = msvcrt.getch()
1473 ans = msvcrt.getch()
1472 if ans in ("q", "Q"):
1474 if ans in ("q", "Q"):
1473 result = False
1475 result = False
1474 else:
1476 else:
1475 result = True
1477 result = True
1476 Term.cout.write("\b"*37 + " "*37 + "\b"*37)
1478 Term.cout.write("\b"*37 + " "*37 + "\b"*37)
1477 return result
1479 return result
1478 else:
1480 else:
1479 def page_more():
1481 def page_more():
1480 ans = raw_input('---Return to continue, q to quit--- ')
1482 ans = raw_input('---Return to continue, q to quit--- ')
1481 if ans.lower().startswith('q'):
1483 if ans.lower().startswith('q'):
1482 return False
1484 return False
1483 else:
1485 else:
1484 return True
1486 return True
1485
1487
1486 esc_re = re.compile(r"(\x1b[^m]+m)")
1488 esc_re = re.compile(r"(\x1b[^m]+m)")
1487
1489
1488 def page_dumb(strng,start=0,screen_lines=25):
1490 def page_dumb(strng,start=0,screen_lines=25):
1489 """Very dumb 'pager' in Python, for when nothing else works.
1491 """Very dumb 'pager' in Python, for when nothing else works.
1490
1492
1491 Only moves forward, same interface as page(), except for pager_cmd and
1493 Only moves forward, same interface as page(), except for pager_cmd and
1492 mode."""
1494 mode."""
1493
1495
1494 out_ln = strng.splitlines()[start:]
1496 out_ln = strng.splitlines()[start:]
1495 screens = chop(out_ln,screen_lines-1)
1497 screens = chop(out_ln,screen_lines-1)
1496 if len(screens) == 1:
1498 if len(screens) == 1:
1497 print >>Term.cout, os.linesep.join(screens[0])
1499 print >>Term.cout, os.linesep.join(screens[0])
1498 else:
1500 else:
1499 last_escape = ""
1501 last_escape = ""
1500 for scr in screens[0:-1]:
1502 for scr in screens[0:-1]:
1501 hunk = os.linesep.join(scr)
1503 hunk = os.linesep.join(scr)
1502 print >>Term.cout, last_escape + hunk
1504 print >>Term.cout, last_escape + hunk
1503 if not page_more():
1505 if not page_more():
1504 return
1506 return
1505 esc_list = esc_re.findall(hunk)
1507 esc_list = esc_re.findall(hunk)
1506 if len(esc_list) > 0:
1508 if len(esc_list) > 0:
1507 last_escape = esc_list[-1]
1509 last_escape = esc_list[-1]
1508 print >>Term.cout, last_escape + os.linesep.join(screens[-1])
1510 print >>Term.cout, last_escape + os.linesep.join(screens[-1])
1509
1511
1510 #----------------------------------------------------------------------------
1512 #----------------------------------------------------------------------------
1511 def page(strng,start=0,screen_lines=0,pager_cmd = None):
1513 def page(strng,start=0,screen_lines=0,pager_cmd = None):
1512 """Print a string, piping through a pager after a certain length.
1514 """Print a string, piping through a pager after a certain length.
1513
1515
1514 The screen_lines parameter specifies the number of *usable* lines of your
1516 The screen_lines parameter specifies the number of *usable* lines of your
1515 terminal screen (total lines minus lines you need to reserve to show other
1517 terminal screen (total lines minus lines you need to reserve to show other
1516 information).
1518 information).
1517
1519
1518 If you set screen_lines to a number <=0, page() will try to auto-determine
1520 If you set screen_lines to a number <=0, page() will try to auto-determine
1519 your screen size and will only use up to (screen_size+screen_lines) for
1521 your screen size and will only use up to (screen_size+screen_lines) for
1520 printing, paging after that. That is, if you want auto-detection but need
1522 printing, paging after that. That is, if you want auto-detection but need
1521 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
1523 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
1522 auto-detection without any lines reserved simply use screen_lines = 0.
1524 auto-detection without any lines reserved simply use screen_lines = 0.
1523
1525
1524 If a string won't fit in the allowed lines, it is sent through the
1526 If a string won't fit in the allowed lines, it is sent through the
1525 specified pager command. If none given, look for PAGER in the environment,
1527 specified pager command. If none given, look for PAGER in the environment,
1526 and ultimately default to less.
1528 and ultimately default to less.
1527
1529
1528 If no system pager works, the string is sent through a 'dumb pager'
1530 If no system pager works, the string is sent through a 'dumb pager'
1529 written in python, very simplistic.
1531 written in python, very simplistic.
1530 """
1532 """
1531
1533
1532 # Some routines may auto-compute start offsets incorrectly and pass a
1534 # Some routines may auto-compute start offsets incorrectly and pass a
1533 # negative value. Offset to 0 for robustness.
1535 # negative value. Offset to 0 for robustness.
1534 start = max(0,start)
1536 start = max(0,start)
1535
1537
1536 # first, try the hook
1538 # first, try the hook
1537 ip = IPython.ipapi.get()
1539 ip = IPython.ipapi.get()
1538 if ip:
1540 if ip:
1539 try:
1541 try:
1540 ip.IP.hooks.show_in_pager(strng)
1542 ip.IP.hooks.show_in_pager(strng)
1541 return
1543 return
1542 except IPython.ipapi.TryNext:
1544 except IPython.ipapi.TryNext:
1543 pass
1545 pass
1544
1546
1545 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
1547 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
1546 TERM = os.environ.get('TERM','dumb')
1548 TERM = os.environ.get('TERM','dumb')
1547 if TERM in ['dumb','emacs'] and os.name != 'nt':
1549 if TERM in ['dumb','emacs'] and os.name != 'nt':
1548 print strng
1550 print strng
1549 return
1551 return
1550 # chop off the topmost part of the string we don't want to see
1552 # chop off the topmost part of the string we don't want to see
1551 str_lines = strng.split(os.linesep)[start:]
1553 str_lines = strng.split(os.linesep)[start:]
1552 str_toprint = os.linesep.join(str_lines)
1554 str_toprint = os.linesep.join(str_lines)
1553 num_newlines = len(str_lines)
1555 num_newlines = len(str_lines)
1554 len_str = len(str_toprint)
1556 len_str = len(str_toprint)
1555
1557
1556 # Dumb heuristics to guesstimate number of on-screen lines the string
1558 # Dumb heuristics to guesstimate number of on-screen lines the string
1557 # takes. Very basic, but good enough for docstrings in reasonable
1559 # takes. Very basic, but good enough for docstrings in reasonable
1558 # terminals. If someone later feels like refining it, it's not hard.
1560 # terminals. If someone later feels like refining it, it's not hard.
1559 numlines = max(num_newlines,int(len_str/80)+1)
1561 numlines = max(num_newlines,int(len_str/80)+1)
1560
1562
1561 if os.name == "nt":
1563 if os.name == "nt":
1562 screen_lines_def = get_console_size(defaulty=25)[1]
1564 screen_lines_def = get_console_size(defaulty=25)[1]
1563 else:
1565 else:
1564 screen_lines_def = 25 # default value if we can't auto-determine
1566 screen_lines_def = 25 # default value if we can't auto-determine
1565
1567
1566 # auto-determine screen size
1568 # auto-determine screen size
1567 if screen_lines <= 0:
1569 if screen_lines <= 0:
1568 if TERM=='xterm':
1570 if TERM=='xterm':
1569 use_curses = USE_CURSES
1571 use_curses = USE_CURSES
1570 else:
1572 else:
1571 # curses causes problems on many terminals other than xterm.
1573 # curses causes problems on many terminals other than xterm.
1572 use_curses = False
1574 use_curses = False
1573 if use_curses:
1575 if use_curses:
1574 # There is a bug in curses, where *sometimes* it fails to properly
1576 # There is a bug in curses, where *sometimes* it fails to properly
1575 # initialize, and then after the endwin() call is made, the
1577 # initialize, and then after the endwin() call is made, the
1576 # terminal is left in an unusable state. Rather than trying to
1578 # terminal is left in an unusable state. Rather than trying to
1577 # check everytime for this (by requesting and comparing termios
1579 # check everytime for this (by requesting and comparing termios
1578 # flags each time), we just save the initial terminal state and
1580 # flags each time), we just save the initial terminal state and
1579 # unconditionally reset it every time. It's cheaper than making
1581 # unconditionally reset it every time. It's cheaper than making
1580 # the checks.
1582 # the checks.
1581 term_flags = termios.tcgetattr(sys.stdout)
1583 term_flags = termios.tcgetattr(sys.stdout)
1582 scr = curses.initscr()
1584 scr = curses.initscr()
1583 screen_lines_real,screen_cols = scr.getmaxyx()
1585 screen_lines_real,screen_cols = scr.getmaxyx()
1584 curses.endwin()
1586 curses.endwin()
1585 # Restore terminal state in case endwin() didn't.
1587 # Restore terminal state in case endwin() didn't.
1586 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
1588 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
1587 # Now we have what we needed: the screen size in rows/columns
1589 # Now we have what we needed: the screen size in rows/columns
1588 screen_lines += screen_lines_real
1590 screen_lines += screen_lines_real
1589 #print '***Screen size:',screen_lines_real,'lines x',\
1591 #print '***Screen size:',screen_lines_real,'lines x',\
1590 #screen_cols,'columns.' # dbg
1592 #screen_cols,'columns.' # dbg
1591 else:
1593 else:
1592 screen_lines += screen_lines_def
1594 screen_lines += screen_lines_def
1593
1595
1594 #print 'numlines',numlines,'screenlines',screen_lines # dbg
1596 #print 'numlines',numlines,'screenlines',screen_lines # dbg
1595 if numlines <= screen_lines :
1597 if numlines <= screen_lines :
1596 #print '*** normal print' # dbg
1598 #print '*** normal print' # dbg
1597 print >>Term.cout, str_toprint
1599 print >>Term.cout, str_toprint
1598 else:
1600 else:
1599 # Try to open pager and default to internal one if that fails.
1601 # Try to open pager and default to internal one if that fails.
1600 # All failure modes are tagged as 'retval=1', to match the return
1602 # All failure modes are tagged as 'retval=1', to match the return
1601 # value of a failed system command. If any intermediate attempt
1603 # value of a failed system command. If any intermediate attempt
1602 # sets retval to 1, at the end we resort to our own page_dumb() pager.
1604 # sets retval to 1, at the end we resort to our own page_dumb() pager.
1603 pager_cmd = get_pager_cmd(pager_cmd)
1605 pager_cmd = get_pager_cmd(pager_cmd)
1604 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1606 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1605 if os.name == 'nt':
1607 if os.name == 'nt':
1606 if pager_cmd.startswith('type'):
1608 if pager_cmd.startswith('type'):
1607 # The default WinXP 'type' command is failing on complex strings.
1609 # The default WinXP 'type' command is failing on complex strings.
1608 retval = 1
1610 retval = 1
1609 else:
1611 else:
1610 tmpname = tempfile.mktemp('.txt')
1612 tmpname = tempfile.mktemp('.txt')
1611 tmpfile = file(tmpname,'wt')
1613 tmpfile = file(tmpname,'wt')
1612 tmpfile.write(strng)
1614 tmpfile.write(strng)
1613 tmpfile.close()
1615 tmpfile.close()
1614 cmd = "%s < %s" % (pager_cmd,tmpname)
1616 cmd = "%s < %s" % (pager_cmd,tmpname)
1615 if os.system(cmd):
1617 if os.system(cmd):
1616 retval = 1
1618 retval = 1
1617 else:
1619 else:
1618 retval = None
1620 retval = None
1619 os.remove(tmpname)
1621 os.remove(tmpname)
1620 else:
1622 else:
1621 try:
1623 try:
1622 retval = None
1624 retval = None
1623 # if I use popen4, things hang. No idea why.
1625 # if I use popen4, things hang. No idea why.
1624 #pager,shell_out = os.popen4(pager_cmd)
1626 #pager,shell_out = os.popen4(pager_cmd)
1625 pager = os.popen(pager_cmd,'w')
1627 pager = os.popen(pager_cmd,'w')
1626 pager.write(strng)
1628 pager.write(strng)
1627 pager.close()
1629 pager.close()
1628 retval = pager.close() # success returns None
1630 retval = pager.close() # success returns None
1629 except IOError,msg: # broken pipe when user quits
1631 except IOError,msg: # broken pipe when user quits
1630 if msg.args == (32,'Broken pipe'):
1632 if msg.args == (32,'Broken pipe'):
1631 retval = None
1633 retval = None
1632 else:
1634 else:
1633 retval = 1
1635 retval = 1
1634 except OSError:
1636 except OSError:
1635 # Other strange problems, sometimes seen in Win2k/cygwin
1637 # Other strange problems, sometimes seen in Win2k/cygwin
1636 retval = 1
1638 retval = 1
1637 if retval is not None:
1639 if retval is not None:
1638 page_dumb(strng,screen_lines=screen_lines)
1640 page_dumb(strng,screen_lines=screen_lines)
1639
1641
1640 #----------------------------------------------------------------------------
1642 #----------------------------------------------------------------------------
1641 def page_file(fname,start = 0, pager_cmd = None):
1643 def page_file(fname,start = 0, pager_cmd = None):
1642 """Page a file, using an optional pager command and starting line.
1644 """Page a file, using an optional pager command and starting line.
1643 """
1645 """
1644
1646
1645 pager_cmd = get_pager_cmd(pager_cmd)
1647 pager_cmd = get_pager_cmd(pager_cmd)
1646 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1648 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1647
1649
1648 try:
1650 try:
1649 if os.environ['TERM'] in ['emacs','dumb']:
1651 if os.environ['TERM'] in ['emacs','dumb']:
1650 raise EnvironmentError
1652 raise EnvironmentError
1651 xsys(pager_cmd + ' ' + fname)
1653 xsys(pager_cmd + ' ' + fname)
1652 except:
1654 except:
1653 try:
1655 try:
1654 if start > 0:
1656 if start > 0:
1655 start -= 1
1657 start -= 1
1656 page(open(fname).read(),start)
1658 page(open(fname).read(),start)
1657 except:
1659 except:
1658 print 'Unable to show file',`fname`
1660 print 'Unable to show file',`fname`
1659
1661
1660
1662
1661 #----------------------------------------------------------------------------
1663 #----------------------------------------------------------------------------
1662 def snip_print(str,width = 75,print_full = 0,header = ''):
1664 def snip_print(str,width = 75,print_full = 0,header = ''):
1663 """Print a string snipping the midsection to fit in width.
1665 """Print a string snipping the midsection to fit in width.
1664
1666
1665 print_full: mode control:
1667 print_full: mode control:
1666 - 0: only snip long strings
1668 - 0: only snip long strings
1667 - 1: send to page() directly.
1669 - 1: send to page() directly.
1668 - 2: snip long strings and ask for full length viewing with page()
1670 - 2: snip long strings and ask for full length viewing with page()
1669 Return 1 if snipping was necessary, 0 otherwise."""
1671 Return 1 if snipping was necessary, 0 otherwise."""
1670
1672
1671 if print_full == 1:
1673 if print_full == 1:
1672 page(header+str)
1674 page(header+str)
1673 return 0
1675 return 0
1674
1676
1675 print header,
1677 print header,
1676 if len(str) < width:
1678 if len(str) < width:
1677 print str
1679 print str
1678 snip = 0
1680 snip = 0
1679 else:
1681 else:
1680 whalf = int((width -5)/2)
1682 whalf = int((width -5)/2)
1681 print str[:whalf] + ' <...> ' + str[-whalf:]
1683 print str[:whalf] + ' <...> ' + str[-whalf:]
1682 snip = 1
1684 snip = 1
1683 if snip and print_full == 2:
1685 if snip and print_full == 2:
1684 if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
1686 if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
1685 page(str)
1687 page(str)
1686 return snip
1688 return snip
1687
1689
1688 #****************************************************************************
1690 #****************************************************************************
1689 # lists, dicts and structures
1691 # lists, dicts and structures
1690
1692
1691 def belong(candidates,checklist):
1693 def belong(candidates,checklist):
1692 """Check whether a list of items appear in a given list of options.
1694 """Check whether a list of items appear in a given list of options.
1693
1695
1694 Returns a list of 1 and 0, one for each candidate given."""
1696 Returns a list of 1 and 0, one for each candidate given."""
1695
1697
1696 return [x in checklist for x in candidates]
1698 return [x in checklist for x in candidates]
1697
1699
1698 #----------------------------------------------------------------------------
1700 #----------------------------------------------------------------------------
1699 def uniq_stable(elems):
1701 def uniq_stable(elems):
1700 """uniq_stable(elems) -> list
1702 """uniq_stable(elems) -> list
1701
1703
1702 Return from an iterable, a list of all the unique elements in the input,
1704 Return from an iterable, a list of all the unique elements in the input,
1703 but maintaining the order in which they first appear.
1705 but maintaining the order in which they first appear.
1704
1706
1705 A naive solution to this problem which just makes a dictionary with the
1707 A naive solution to this problem which just makes a dictionary with the
1706 elements as keys fails to respect the stability condition, since
1708 elements as keys fails to respect the stability condition, since
1707 dictionaries are unsorted by nature.
1709 dictionaries are unsorted by nature.
1708
1710
1709 Note: All elements in the input must be valid dictionary keys for this
1711 Note: All elements in the input must be valid dictionary keys for this
1710 routine to work, as it internally uses a dictionary for efficiency
1712 routine to work, as it internally uses a dictionary for efficiency
1711 reasons."""
1713 reasons."""
1712
1714
1713 unique = []
1715 unique = []
1714 unique_dict = {}
1716 unique_dict = {}
1715 for nn in elems:
1717 for nn in elems:
1716 if nn not in unique_dict:
1718 if nn not in unique_dict:
1717 unique.append(nn)
1719 unique.append(nn)
1718 unique_dict[nn] = None
1720 unique_dict[nn] = None
1719 return unique
1721 return unique
1720
1722
1721 #----------------------------------------------------------------------------
1723 #----------------------------------------------------------------------------
1722 class NLprinter:
1724 class NLprinter:
1723 """Print an arbitrarily nested list, indicating index numbers.
1725 """Print an arbitrarily nested list, indicating index numbers.
1724
1726
1725 An instance of this class called nlprint is available and callable as a
1727 An instance of this class called nlprint is available and callable as a
1726 function.
1728 function.
1727
1729
1728 nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent'
1730 nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent'
1729 and using 'sep' to separate the index from the value. """
1731 and using 'sep' to separate the index from the value. """
1730
1732
1731 def __init__(self):
1733 def __init__(self):
1732 self.depth = 0
1734 self.depth = 0
1733
1735
1734 def __call__(self,lst,pos='',**kw):
1736 def __call__(self,lst,pos='',**kw):
1735 """Prints the nested list numbering levels."""
1737 """Prints the nested list numbering levels."""
1736 kw.setdefault('indent',' ')
1738 kw.setdefault('indent',' ')
1737 kw.setdefault('sep',': ')
1739 kw.setdefault('sep',': ')
1738 kw.setdefault('start',0)
1740 kw.setdefault('start',0)
1739 kw.setdefault('stop',len(lst))
1741 kw.setdefault('stop',len(lst))
1740 # we need to remove start and stop from kw so they don't propagate
1742 # we need to remove start and stop from kw so they don't propagate
1741 # into a recursive call for a nested list.
1743 # into a recursive call for a nested list.
1742 start = kw['start']; del kw['start']
1744 start = kw['start']; del kw['start']
1743 stop = kw['stop']; del kw['stop']
1745 stop = kw['stop']; del kw['stop']
1744 if self.depth == 0 and 'header' in kw.keys():
1746 if self.depth == 0 and 'header' in kw.keys():
1745 print kw['header']
1747 print kw['header']
1746
1748
1747 for idx in range(start,stop):
1749 for idx in range(start,stop):
1748 elem = lst[idx]
1750 elem = lst[idx]
1749 if type(elem)==type([]):
1751 if type(elem)==type([]):
1750 self.depth += 1
1752 self.depth += 1
1751 self.__call__(elem,itpl('$pos$idx,'),**kw)
1753 self.__call__(elem,itpl('$pos$idx,'),**kw)
1752 self.depth -= 1
1754 self.depth -= 1
1753 else:
1755 else:
1754 printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem')
1756 printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem')
1755
1757
1756 nlprint = NLprinter()
1758 nlprint = NLprinter()
1757 #----------------------------------------------------------------------------
1759 #----------------------------------------------------------------------------
1758 def all_belong(candidates,checklist):
1760 def all_belong(candidates,checklist):
1759 """Check whether a list of items ALL appear in a given list of options.
1761 """Check whether a list of items ALL appear in a given list of options.
1760
1762
1761 Returns a single 1 or 0 value."""
1763 Returns a single 1 or 0 value."""
1762
1764
1763 return 1-(0 in [x in checklist for x in candidates])
1765 return 1-(0 in [x in checklist for x in candidates])
1764
1766
1765 #----------------------------------------------------------------------------
1767 #----------------------------------------------------------------------------
1766 def sort_compare(lst1,lst2,inplace = 1):
1768 def sort_compare(lst1,lst2,inplace = 1):
1767 """Sort and compare two lists.
1769 """Sort and compare two lists.
1768
1770
1769 By default it does it in place, thus modifying the lists. Use inplace = 0
1771 By default it does it in place, thus modifying the lists. Use inplace = 0
1770 to avoid that (at the cost of temporary copy creation)."""
1772 to avoid that (at the cost of temporary copy creation)."""
1771 if not inplace:
1773 if not inplace:
1772 lst1 = lst1[:]
1774 lst1 = lst1[:]
1773 lst2 = lst2[:]
1775 lst2 = lst2[:]
1774 lst1.sort(); lst2.sort()
1776 lst1.sort(); lst2.sort()
1775 return lst1 == lst2
1777 return lst1 == lst2
1776
1778
1777 #----------------------------------------------------------------------------
1779 #----------------------------------------------------------------------------
1778 def mkdict(**kwargs):
1780 def mkdict(**kwargs):
1779 """Return a dict from a keyword list.
1781 """Return a dict from a keyword list.
1780
1782
1781 It's just syntactic sugar for making ditcionary creation more convenient:
1783 It's just syntactic sugar for making ditcionary creation more convenient:
1782 # the standard way
1784 # the standard way
1783 >>>data = { 'red' : 1, 'green' : 2, 'blue' : 3 }
1785 >>>data = { 'red' : 1, 'green' : 2, 'blue' : 3 }
1784 # a cleaner way
1786 # a cleaner way
1785 >>>data = dict(red=1, green=2, blue=3)
1787 >>>data = dict(red=1, green=2, blue=3)
1786
1788
1787 If you need more than this, look at the Struct() class."""
1789 If you need more than this, look at the Struct() class."""
1788
1790
1789 return kwargs
1791 return kwargs
1790
1792
1791 #----------------------------------------------------------------------------
1793 #----------------------------------------------------------------------------
1792 def list2dict(lst):
1794 def list2dict(lst):
1793 """Takes a list of (key,value) pairs and turns it into a dict."""
1795 """Takes a list of (key,value) pairs and turns it into a dict."""
1794
1796
1795 dic = {}
1797 dic = {}
1796 for k,v in lst: dic[k] = v
1798 for k,v in lst: dic[k] = v
1797 return dic
1799 return dic
1798
1800
1799 #----------------------------------------------------------------------------
1801 #----------------------------------------------------------------------------
1800 def list2dict2(lst,default=''):
1802 def list2dict2(lst,default=''):
1801 """Takes a list and turns it into a dict.
1803 """Takes a list and turns it into a dict.
1802 Much slower than list2dict, but more versatile. This version can take
1804 Much slower than list2dict, but more versatile. This version can take
1803 lists with sublists of arbitrary length (including sclars)."""
1805 lists with sublists of arbitrary length (including sclars)."""
1804
1806
1805 dic = {}
1807 dic = {}
1806 for elem in lst:
1808 for elem in lst:
1807 if type(elem) in (types.ListType,types.TupleType):
1809 if type(elem) in (types.ListType,types.TupleType):
1808 size = len(elem)
1810 size = len(elem)
1809 if size == 0:
1811 if size == 0:
1810 pass
1812 pass
1811 elif size == 1:
1813 elif size == 1:
1812 dic[elem] = default
1814 dic[elem] = default
1813 else:
1815 else:
1814 k,v = elem[0], elem[1:]
1816 k,v = elem[0], elem[1:]
1815 if len(v) == 1: v = v[0]
1817 if len(v) == 1: v = v[0]
1816 dic[k] = v
1818 dic[k] = v
1817 else:
1819 else:
1818 dic[elem] = default
1820 dic[elem] = default
1819 return dic
1821 return dic
1820
1822
1821 #----------------------------------------------------------------------------
1823 #----------------------------------------------------------------------------
1822 def flatten(seq):
1824 def flatten(seq):
1823 """Flatten a list of lists (NOT recursive, only works for 2d lists)."""
1825 """Flatten a list of lists (NOT recursive, only works for 2d lists)."""
1824
1826
1825 return [x for subseq in seq for x in subseq]
1827 return [x for subseq in seq for x in subseq]
1826
1828
1827 #----------------------------------------------------------------------------
1829 #----------------------------------------------------------------------------
1828 def get_slice(seq,start=0,stop=None,step=1):
1830 def get_slice(seq,start=0,stop=None,step=1):
1829 """Get a slice of a sequence with variable step. Specify start,stop,step."""
1831 """Get a slice of a sequence with variable step. Specify start,stop,step."""
1830 if stop == None:
1832 if stop == None:
1831 stop = len(seq)
1833 stop = len(seq)
1832 item = lambda i: seq[i]
1834 item = lambda i: seq[i]
1833 return map(item,xrange(start,stop,step))
1835 return map(item,xrange(start,stop,step))
1834
1836
1835 #----------------------------------------------------------------------------
1837 #----------------------------------------------------------------------------
1836 def chop(seq,size):
1838 def chop(seq,size):
1837 """Chop a sequence into chunks of the given size."""
1839 """Chop a sequence into chunks of the given size."""
1838 chunk = lambda i: seq[i:i+size]
1840 chunk = lambda i: seq[i:i+size]
1839 return map(chunk,xrange(0,len(seq),size))
1841 return map(chunk,xrange(0,len(seq),size))
1840
1842
1841 #----------------------------------------------------------------------------
1843 #----------------------------------------------------------------------------
1842 # with is a keyword as of python 2.5, so this function is renamed to withobj
1844 # with is a keyword as of python 2.5, so this function is renamed to withobj
1843 # from its old 'with' name.
1845 # from its old 'with' name.
1844 def with_obj(object, **args):
1846 def with_obj(object, **args):
1845 """Set multiple attributes for an object, similar to Pascal's with.
1847 """Set multiple attributes for an object, similar to Pascal's with.
1846
1848
1847 Example:
1849 Example:
1848 with_obj(jim,
1850 with_obj(jim,
1849 born = 1960,
1851 born = 1960,
1850 haircolour = 'Brown',
1852 haircolour = 'Brown',
1851 eyecolour = 'Green')
1853 eyecolour = 'Green')
1852
1854
1853 Credit: Greg Ewing, in
1855 Credit: Greg Ewing, in
1854 http://mail.python.org/pipermail/python-list/2001-May/040703.html.
1856 http://mail.python.org/pipermail/python-list/2001-May/040703.html.
1855
1857
1856 NOTE: up until IPython 0.7.2, this was called simply 'with', but 'with'
1858 NOTE: up until IPython 0.7.2, this was called simply 'with', but 'with'
1857 has become a keyword for Python 2.5, so we had to rename it."""
1859 has become a keyword for Python 2.5, so we had to rename it."""
1858
1860
1859 object.__dict__.update(args)
1861 object.__dict__.update(args)
1860
1862
1861 #----------------------------------------------------------------------------
1863 #----------------------------------------------------------------------------
1862 def setattr_list(obj,alist,nspace = None):
1864 def setattr_list(obj,alist,nspace = None):
1863 """Set a list of attributes for an object taken from a namespace.
1865 """Set a list of attributes for an object taken from a namespace.
1864
1866
1865 setattr_list(obj,alist,nspace) -> sets in obj all the attributes listed in
1867 setattr_list(obj,alist,nspace) -> sets in obj all the attributes listed in
1866 alist with their values taken from nspace, which must be a dict (something
1868 alist with their values taken from nspace, which must be a dict (something
1867 like locals() will often do) If nspace isn't given, locals() of the
1869 like locals() will often do) If nspace isn't given, locals() of the
1868 *caller* is used, so in most cases you can omit it.
1870 *caller* is used, so in most cases you can omit it.
1869
1871
1870 Note that alist can be given as a string, which will be automatically
1872 Note that alist can be given as a string, which will be automatically
1871 split into a list on whitespace. If given as a list, it must be a list of
1873 split into a list on whitespace. If given as a list, it must be a list of
1872 *strings* (the variable names themselves), not of variables."""
1874 *strings* (the variable names themselves), not of variables."""
1873
1875
1874 # this grabs the local variables from the *previous* call frame -- that is
1876 # this grabs the local variables from the *previous* call frame -- that is
1875 # the locals from the function that called setattr_list().
1877 # the locals from the function that called setattr_list().
1876 # - snipped from weave.inline()
1878 # - snipped from weave.inline()
1877 if nspace is None:
1879 if nspace is None:
1878 call_frame = sys._getframe().f_back
1880 call_frame = sys._getframe().f_back
1879 nspace = call_frame.f_locals
1881 nspace = call_frame.f_locals
1880
1882
1881 if type(alist) in StringTypes:
1883 if type(alist) in StringTypes:
1882 alist = alist.split()
1884 alist = alist.split()
1883 for attr in alist:
1885 for attr in alist:
1884 val = eval(attr,nspace)
1886 val = eval(attr,nspace)
1885 setattr(obj,attr,val)
1887 setattr(obj,attr,val)
1886
1888
1887 #----------------------------------------------------------------------------
1889 #----------------------------------------------------------------------------
1888 def getattr_list(obj,alist,*args):
1890 def getattr_list(obj,alist,*args):
1889 """getattr_list(obj,alist[, default]) -> attribute list.
1891 """getattr_list(obj,alist[, default]) -> attribute list.
1890
1892
1891 Get a list of named attributes for an object. When a default argument is
1893 Get a list of named attributes for an object. When a default argument is
1892 given, it is returned when the attribute doesn't exist; without it, an
1894 given, it is returned when the attribute doesn't exist; without it, an
1893 exception is raised in that case.
1895 exception is raised in that case.
1894
1896
1895 Note that alist can be given as a string, which will be automatically
1897 Note that alist can be given as a string, which will be automatically
1896 split into a list on whitespace. If given as a list, it must be a list of
1898 split into a list on whitespace. If given as a list, it must be a list of
1897 *strings* (the variable names themselves), not of variables."""
1899 *strings* (the variable names themselves), not of variables."""
1898
1900
1899 if type(alist) in StringTypes:
1901 if type(alist) in StringTypes:
1900 alist = alist.split()
1902 alist = alist.split()
1901 if args:
1903 if args:
1902 if len(args)==1:
1904 if len(args)==1:
1903 default = args[0]
1905 default = args[0]
1904 return map(lambda attr: getattr(obj,attr,default),alist)
1906 return map(lambda attr: getattr(obj,attr,default),alist)
1905 else:
1907 else:
1906 raise ValueError,'getattr_list() takes only one optional argument'
1908 raise ValueError,'getattr_list() takes only one optional argument'
1907 else:
1909 else:
1908 return map(lambda attr: getattr(obj,attr),alist)
1910 return map(lambda attr: getattr(obj,attr),alist)
1909
1911
1910 #----------------------------------------------------------------------------
1912 #----------------------------------------------------------------------------
1911 def map_method(method,object_list,*argseq,**kw):
1913 def map_method(method,object_list,*argseq,**kw):
1912 """map_method(method,object_list,*args,**kw) -> list
1914 """map_method(method,object_list,*args,**kw) -> list
1913
1915
1914 Return a list of the results of applying the methods to the items of the
1916 Return a list of the results of applying the methods to the items of the
1915 argument sequence(s). If more than one sequence is given, the method is
1917 argument sequence(s). If more than one sequence is given, the method is
1916 called with an argument list consisting of the corresponding item of each
1918 called with an argument list consisting of the corresponding item of each
1917 sequence. All sequences must be of the same length.
1919 sequence. All sequences must be of the same length.
1918
1920
1919 Keyword arguments are passed verbatim to all objects called.
1921 Keyword arguments are passed verbatim to all objects called.
1920
1922
1921 This is Python code, so it's not nearly as fast as the builtin map()."""
1923 This is Python code, so it's not nearly as fast as the builtin map()."""
1922
1924
1923 out_list = []
1925 out_list = []
1924 idx = 0
1926 idx = 0
1925 for object in object_list:
1927 for object in object_list:
1926 try:
1928 try:
1927 handler = getattr(object, method)
1929 handler = getattr(object, method)
1928 except AttributeError:
1930 except AttributeError:
1929 out_list.append(None)
1931 out_list.append(None)
1930 else:
1932 else:
1931 if argseq:
1933 if argseq:
1932 args = map(lambda lst:lst[idx],argseq)
1934 args = map(lambda lst:lst[idx],argseq)
1933 #print 'ob',object,'hand',handler,'ar',args # dbg
1935 #print 'ob',object,'hand',handler,'ar',args # dbg
1934 out_list.append(handler(args,**kw))
1936 out_list.append(handler(args,**kw))
1935 else:
1937 else:
1936 out_list.append(handler(**kw))
1938 out_list.append(handler(**kw))
1937 idx += 1
1939 idx += 1
1938 return out_list
1940 return out_list
1939
1941
1940 #----------------------------------------------------------------------------
1942 #----------------------------------------------------------------------------
1941 def get_class_members(cls):
1943 def get_class_members(cls):
1942 ret = dir(cls)
1944 ret = dir(cls)
1943 if hasattr(cls,'__bases__'):
1945 if hasattr(cls,'__bases__'):
1944 for base in cls.__bases__:
1946 for base in cls.__bases__:
1945 ret.extend(get_class_members(base))
1947 ret.extend(get_class_members(base))
1946 return ret
1948 return ret
1947
1949
1948 #----------------------------------------------------------------------------
1950 #----------------------------------------------------------------------------
1949 def dir2(obj):
1951 def dir2(obj):
1950 """dir2(obj) -> list of strings
1952 """dir2(obj) -> list of strings
1951
1953
1952 Extended version of the Python builtin dir(), which does a few extra
1954 Extended version of the Python builtin dir(), which does a few extra
1953 checks, and supports common objects with unusual internals that confuse
1955 checks, and supports common objects with unusual internals that confuse
1954 dir(), such as Traits and PyCrust.
1956 dir(), such as Traits and PyCrust.
1955
1957
1956 This version is guaranteed to return only a list of true strings, whereas
1958 This version is guaranteed to return only a list of true strings, whereas
1957 dir() returns anything that objects inject into themselves, even if they
1959 dir() returns anything that objects inject into themselves, even if they
1958 are later not really valid for attribute access (many extension libraries
1960 are later not really valid for attribute access (many extension libraries
1959 have such bugs).
1961 have such bugs).
1960 """
1962 """
1961
1963
1962 # Start building the attribute list via dir(), and then complete it
1964 # Start building the attribute list via dir(), and then complete it
1963 # with a few extra special-purpose calls.
1965 # with a few extra special-purpose calls.
1964 words = dir(obj)
1966 words = dir(obj)
1965
1967
1966 if hasattr(obj,'__class__'):
1968 if hasattr(obj,'__class__'):
1967 words.append('__class__')
1969 words.append('__class__')
1968 words.extend(get_class_members(obj.__class__))
1970 words.extend(get_class_members(obj.__class__))
1969 #if '__base__' in words: 1/0
1971 #if '__base__' in words: 1/0
1970
1972
1971 # Some libraries (such as traits) may introduce duplicates, we want to
1973 # Some libraries (such as traits) may introduce duplicates, we want to
1972 # track and clean this up if it happens
1974 # track and clean this up if it happens
1973 may_have_dupes = False
1975 may_have_dupes = False
1974
1976
1975 # this is the 'dir' function for objects with Enthought's traits
1977 # this is the 'dir' function for objects with Enthought's traits
1976 if hasattr(obj, 'trait_names'):
1978 if hasattr(obj, 'trait_names'):
1977 try:
1979 try:
1978 words.extend(obj.trait_names())
1980 words.extend(obj.trait_names())
1979 may_have_dupes = True
1981 may_have_dupes = True
1980 except TypeError:
1982 except TypeError:
1981 # This will happen if `obj` is a class and not an instance.
1983 # This will happen if `obj` is a class and not an instance.
1982 pass
1984 pass
1983
1985
1984 # Support for PyCrust-style _getAttributeNames magic method.
1986 # Support for PyCrust-style _getAttributeNames magic method.
1985 if hasattr(obj, '_getAttributeNames'):
1987 if hasattr(obj, '_getAttributeNames'):
1986 try:
1988 try:
1987 words.extend(obj._getAttributeNames())
1989 words.extend(obj._getAttributeNames())
1988 may_have_dupes = True
1990 may_have_dupes = True
1989 except TypeError:
1991 except TypeError:
1990 # `obj` is a class and not an instance. Ignore
1992 # `obj` is a class and not an instance. Ignore
1991 # this error.
1993 # this error.
1992 pass
1994 pass
1993
1995
1994 if may_have_dupes:
1996 if may_have_dupes:
1995 # eliminate possible duplicates, as some traits may also
1997 # eliminate possible duplicates, as some traits may also
1996 # appear as normal attributes in the dir() call.
1998 # appear as normal attributes in the dir() call.
1997 words = list(set(words))
1999 words = list(set(words))
1998 words.sort()
2000 words.sort()
1999
2001
2000 # filter out non-string attributes which may be stuffed by dir() calls
2002 # filter out non-string attributes which may be stuffed by dir() calls
2001 # and poor coding in third-party modules
2003 # and poor coding in third-party modules
2002 return [w for w in words if isinstance(w, basestring)]
2004 return [w for w in words if isinstance(w, basestring)]
2003
2005
2004 #----------------------------------------------------------------------------
2006 #----------------------------------------------------------------------------
2005 def import_fail_info(mod_name,fns=None):
2007 def import_fail_info(mod_name,fns=None):
2006 """Inform load failure for a module."""
2008 """Inform load failure for a module."""
2007
2009
2008 if fns == None:
2010 if fns == None:
2009 warn("Loading of %s failed.\n" % (mod_name,))
2011 warn("Loading of %s failed.\n" % (mod_name,))
2010 else:
2012 else:
2011 warn("Loading of %s from %s failed.\n" % (fns,mod_name))
2013 warn("Loading of %s from %s failed.\n" % (fns,mod_name))
2012
2014
2013 #----------------------------------------------------------------------------
2015 #----------------------------------------------------------------------------
2014 # Proposed popitem() extension, written as a method
2016 # Proposed popitem() extension, written as a method
2015
2017
2016
2018
2017 class NotGiven: pass
2019 class NotGiven: pass
2018
2020
2019 def popkey(dct,key,default=NotGiven):
2021 def popkey(dct,key,default=NotGiven):
2020 """Return dct[key] and delete dct[key].
2022 """Return dct[key] and delete dct[key].
2021
2023
2022 If default is given, return it if dct[key] doesn't exist, otherwise raise
2024 If default is given, return it if dct[key] doesn't exist, otherwise raise
2023 KeyError. """
2025 KeyError. """
2024
2026
2025 try:
2027 try:
2026 val = dct[key]
2028 val = dct[key]
2027 except KeyError:
2029 except KeyError:
2028 if default is NotGiven:
2030 if default is NotGiven:
2029 raise
2031 raise
2030 else:
2032 else:
2031 return default
2033 return default
2032 else:
2034 else:
2033 del dct[key]
2035 del dct[key]
2034 return val
2036 return val
2035
2037
2036 def wrap_deprecated(func, suggest = '<nothing>'):
2038 def wrap_deprecated(func, suggest = '<nothing>'):
2037 def newFunc(*args, **kwargs):
2039 def newFunc(*args, **kwargs):
2038 warnings.warn("Call to deprecated function %s, use %s instead" %
2040 warnings.warn("Call to deprecated function %s, use %s instead" %
2039 ( func.__name__, suggest),
2041 ( func.__name__, suggest),
2040 category=DeprecationWarning,
2042 category=DeprecationWarning,
2041 stacklevel = 2)
2043 stacklevel = 2)
2042 return func(*args, **kwargs)
2044 return func(*args, **kwargs)
2043 return newFunc
2045 return newFunc
2044
2045 #*************************** end of file <genutils.py> **********************
2046
2046
2047
2048 def _num_cpus_unix():
2049 """Return the number of active CPUs on a Unix system."""
2050 return os.sysconf("SC_NPROCESSORS_ONLN")
2051
2052
2053 def _num_cpus_darwin():
2054 """Return the number of active CPUs on a Darwin system."""
2055 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
2056 return p.stdout.read()
2057
2058
2059 def _num_cpus_windows():
2060 """Return the number of active CPUs on a Windows system."""
2061 return os.environ.get("NUMBER_OF_PROCESSORS")
2062
2063
2064 def num_cpus():
2065 """Return the effective number of CPUs in the system as an integer.
2066
2067 This cross-platform function makes an attempt at finding the total number of
2068 available CPUs in the system, as returned by various underlying system and
2069 python calls.
2070
2071 If it can't find a sensible answer, it returns 1 (though an error *may* make
2072 it return a large positive number that's actually incorrect).
2073 """
2074
2075 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
2076 # for the names of the keys we needed to look up for this function. This
2077 # code was inspired by their equivalent function.
2078
2079 ncpufuncs = {'Linux':_num_cpus_unix,
2080 'Darwin':_num_cpus_darwin,
2081 'Windows':_num_cpus_windows,
2082 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
2083 # See http://bugs.python.org/issue1082 for details.
2084 'Microsoft':_num_cpus_windows,
2085 }
2086
2087 ncpufunc = ncpufuncs.get(platform.system(),
2088 # default to unix version (Solaris, AIX, etc)
2089 _num_cpus_unix)
2090
2091 try:
2092 ncpus = max(1,int(ncpufunc()))
2093 except:
2094 ncpus = 1
2095 return ncpus
2096
2097 #*************************** end of file <genutils.py> **********************
@@ -1,394 +1,406 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Mimic C structs with lots of extra functionality.
2 """Mimic C structs with lots of extra functionality.
3
3
4 $Id: ipstruct.py 1950 2006-11-28 19:15:35Z vivainio $"""
4 $Id: ipstruct.py 1950 2006-11-28 19:15:35Z vivainio $"""
5
5
6 #*****************************************************************************
6 #*****************************************************************************
7 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
7 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #*****************************************************************************
11 #*****************************************************************************
12
12
13 from IPython import Release
13 from IPython import Release
14 __author__ = '%s <%s>' % Release.authors['Fernando']
14 __author__ = '%s <%s>' % Release.authors['Fernando']
15 __license__ = Release.license
15 __license__ = Release.license
16
16
17 __all__ = ['Struct']
17 __all__ = ['Struct']
18
18
19 import types
19 import types
20 import pprint
20 import pprint
21
21
22 from IPython.genutils import list2dict2
22 from IPython.genutils import list2dict2
23
23
24 class Struct:
24 class Struct:
25 """Class to mimic C structs but also provide convenient dictionary-like
25 """Class to mimic C structs but also provide convenient dictionary-like
26 functionality.
26 functionality.
27
27
28 Instances can be initialized with a dictionary, a list of key=value pairs
28 Instances can be initialized with a dictionary, a list of key=value pairs
29 or both. If both are present, the dictionary must come first.
29 or both. If both are present, the dictionary must come first.
30
30
31 Because Python classes provide direct assignment to their members, it's
31 Because Python classes provide direct assignment to their members, it's
32 easy to overwrite normal methods (S.copy = 1 would destroy access to
32 easy to overwrite normal methods (S.copy = 1 would destroy access to
33 S.copy()). For this reason, all builtin method names are protected and
33 S.copy()). For this reason, all builtin method names are protected and
34 can't be assigned to. An attempt to do s.copy=1 or s['copy']=1 will raise
34 can't be assigned to. An attempt to do s.copy=1 or s['copy']=1 will raise
35 a KeyError exception. If you really want to, you can bypass this
35 a KeyError exception. If you really want to, you can bypass this
36 protection by directly assigning to __dict__: s.__dict__['copy']=1 will
36 protection by directly assigning to __dict__: s.__dict__['copy']=1 will
37 still work. Doing this will break functionality, though. As in most of
37 still work. Doing this will break functionality, though. As in most of
38 Python, namespace protection is weakly enforced, so feel free to shoot
38 Python, namespace protection is weakly enforced, so feel free to shoot
39 yourself if you really want to.
39 yourself if you really want to.
40
40
41 Note that this class uses more memory and is *much* slower than a regular
41 Note that this class uses more memory and is *much* slower than a regular
42 dictionary, so be careful in situations where memory or performance are
42 dictionary, so be careful in situations where memory or performance are
43 critical. But for day to day use it should behave fine. It is particularly
43 critical. But for day to day use it should behave fine. It is particularly
44 convenient for storing configuration data in programs.
44 convenient for storing configuration data in programs.
45
45
46 +,+=,- and -= are implemented. +/+= do merges (non-destructive updates),
46 +,+=,- and -= are implemented. +/+= do merges (non-destructive updates),
47 -/-= remove keys from the original. See the method descripitions.
47 -/-= remove keys from the original. See the method descripitions.
48
48
49 This class allows a quick access syntax: both s.key and s['key'] are
49 This class allows a quick access syntax: both s.key and s['key'] are
50 valid. This syntax has a limitation: each 'key' has to be explicitly
50 valid. This syntax has a limitation: each 'key' has to be explicitly
51 accessed by its original name. The normal s.key syntax doesn't provide
51 accessed by its original name. The normal s.key syntax doesn't provide
52 access to the keys via variables whose values evaluate to the desired
52 access to the keys via variables whose values evaluate to the desired
53 keys. An example should clarify this:
53 keys. An example should clarify this:
54
54
55 Define a dictionary and initialize both with dict and k=v pairs:
55 Define a dictionary and initialize both with dict and k=v pairs:
56 >>> d={'a':1,'b':2}
56 >>> d={'a':1,'b':2}
57 >>> s=Struct(d,hi=10,ho=20)
57 >>> s=Struct(d,hi=10,ho=20)
58
58 The return of __repr__ can be used to create a new instance:
59 The return of __repr__ can be used to create a new instance:
59 >>> s
60 >>> s
60 Struct({'ho': 20, 'b': 2, 'hi': 10, 'a': 1})
61 Struct({'__allownew': True, 'a': 1, 'b': 2, 'hi': 10, 'ho': 20})
62
63 Note: the special '__allownew' key is used for internal purposes.
64
61 __str__ (called by print) shows it's not quite a regular dictionary:
65 __str__ (called by print) shows it's not quite a regular dictionary:
62 >>> print s
66 >>> print s
63 Struct {a: 1, b: 2, hi: 10, ho: 20}
67 Struct({'__allownew': True, 'a': 1, 'b': 2, 'hi': 10, 'ho': 20})
68
64 Access by explicitly named key with dot notation:
69 Access by explicitly named key with dot notation:
65 >>> s.a
70 >>> s.a
66 1
71 1
72
67 Or like a dictionary:
73 Or like a dictionary:
68 >>> s['a']
74 >>> s['a']
69 1
75 1
76
70 If you want a variable to hold the key value, only dictionary access works:
77 If you want a variable to hold the key value, only dictionary access works:
71 >>> key='hi'
78 >>> key='hi'
72 >>> s.key
79 >>> s.key
73 Traceback (most recent call last):
80 Traceback (most recent call last):
74 File "<stdin>", line 1, in ?
81 File "<stdin>", line 1, in ?
75 AttributeError: Struct instance has no attribute 'key'
82 AttributeError: Struct instance has no attribute 'key'
83
76 >>> s[key]
84 >>> s[key]
77 10
85 10
78
86
79 Another limitation of the s.key syntax (and Struct(key=val)
87 Another limitation of the s.key syntax (and Struct(key=val)
80 initialization): keys can't be numbers. But numeric keys can be used and
88 initialization): keys can't be numbers. But numeric keys can be used and
81 accessed using the dictionary syntax. Again, an example:
89 accessed using the dictionary syntax. Again, an example:
82
90
83 This doesn't work:
91 This doesn't work:
84 >>> s=Struct(4='hi')
92 >>> s=Struct(4='hi') #doctest: +IGNORE_EXCEPTION_DETAIL
93 Traceback (most recent call last):
94 ...
85 SyntaxError: keyword can't be an expression
95 SyntaxError: keyword can't be an expression
96
86 But this does:
97 But this does:
87 >>> s=Struct()
98 >>> s=Struct()
88 >>> s[4]='hi'
99 >>> s[4]='hi'
89 >>> s
100 >>> s
90 Struct({4: 'hi'})
101 Struct({4: 'hi', '__allownew': True})
91 >>> s[4]
102 >>> s[4]
92 'hi'
103 'hi'
93 """
104 """
94
105
95 # Attributes to which __setitem__ and __setattr__ will block access.
106 # Attributes to which __setitem__ and __setattr__ will block access.
96 # Note: much of this will be moot in Python 2.2 and will be done in a much
107 # Note: much of this will be moot in Python 2.2 and will be done in a much
97 # cleaner way.
108 # cleaner way.
98 __protected = ('copy dict dictcopy get has_attr has_key items keys '
109 __protected = ('copy dict dictcopy get has_attr has_key items keys '
99 'merge popitem setdefault update values '
110 'merge popitem setdefault update values '
100 '__make_dict __dict_invert ').split()
111 '__make_dict __dict_invert ').split()
101
112
102 def __init__(self,dict=None,**kw):
113 def __init__(self,dict=None,**kw):
103 """Initialize with a dictionary, another Struct, or by giving
114 """Initialize with a dictionary, another Struct, or by giving
104 explicitly the list of attributes.
115 explicitly the list of attributes.
105
116
106 Both can be used, but the dictionary must come first:
117 Both can be used, but the dictionary must come first:
107 Struct(dict), Struct(k1=v1,k2=v2) or Struct(dict,k1=v1,k2=v2).
118 Struct(dict), Struct(k1=v1,k2=v2) or Struct(dict,k1=v1,k2=v2).
108 """
119 """
109 self.__dict__['__allownew'] = True
120 self.__dict__['__allownew'] = True
110 if dict is None:
121 if dict is None:
111 dict = {}
122 dict = {}
112 if isinstance(dict,Struct):
123 if isinstance(dict,Struct):
113 dict = dict.dict()
124 dict = dict.dict()
114 elif dict and type(dict) is not types.DictType:
125 elif dict and type(dict) is not types.DictType:
115 raise TypeError,\
126 raise TypeError,\
116 'Initialize with a dictionary or key=val pairs.'
127 'Initialize with a dictionary or key=val pairs.'
117 dict.update(kw)
128 dict.update(kw)
118 # do the updating by hand to guarantee that we go through the
129 # do the updating by hand to guarantee that we go through the
119 # safety-checked __setitem__
130 # safety-checked __setitem__
120 for k,v in dict.items():
131 for k,v in dict.items():
121 self[k] = v
132 self[k] = v
122
133
123
134
124 def __setitem__(self,key,value):
135 def __setitem__(self,key,value):
125 """Used when struct[key] = val calls are made."""
136 """Used when struct[key] = val calls are made."""
126 if key in Struct.__protected:
137 if key in Struct.__protected:
127 raise KeyError,'Key '+`key`+' is a protected key of class Struct.'
138 raise KeyError,'Key '+`key`+' is a protected key of class Struct.'
128 if not self['__allownew'] and key not in self.__dict__:
139 if not self['__allownew'] and key not in self.__dict__:
129 raise KeyError(
140 raise KeyError(
130 "Can't create unknown attribute %s - Check for typos, or use allow_new_attr to create new attributes!" %
141 "Can't create unknown attribute %s - Check for typos, or use allow_new_attr to create new attributes!" %
131 key)
142 key)
132
143
133 self.__dict__[key] = value
144 self.__dict__[key] = value
134
145
135 def __setattr__(self, key, value):
146 def __setattr__(self, key, value):
136 """Used when struct.key = val calls are made."""
147 """Used when struct.key = val calls are made."""
137 self.__setitem__(key,value)
148 self.__setitem__(key,value)
138
149
139 def __str__(self):
150 def __str__(self):
140 """Gets called by print."""
151 """Gets called by print."""
141
152
142 return 'Struct('+ pprint.pformat(self.__dict__)+')'
153 return 'Struct('+ pprint.pformat(self.__dict__)+')'
143
154
144 def __repr__(self):
155 def __repr__(self):
145 """Gets called by repr.
156 """Gets called by repr.
146
157
147 A Struct can be recreated with S_new=eval(repr(S_old))."""
158 A Struct can be recreated with S_new=eval(repr(S_old))."""
148 return self.__str__()
159 return self.__str__()
149
160
150 def __getitem__(self,key):
161 def __getitem__(self,key):
151 """Allows struct[key] access."""
162 """Allows struct[key] access."""
152 return self.__dict__[key]
163 return self.__dict__[key]
153
164
154 def __contains__(self,key):
165 def __contains__(self,key):
155 """Allows use of the 'in' operator."""
166 """Allows use of the 'in' operator."""
156 return self.__dict__.has_key(key)
167 return self.__dict__.has_key(key)
157
168
158 def __iadd__(self,other):
169 def __iadd__(self,other):
159 """S += S2 is a shorthand for S.merge(S2)."""
170 """S += S2 is a shorthand for S.merge(S2)."""
160 self.merge(other)
171 self.merge(other)
161 return self
172 return self
162
173
163 def __add__(self,other):
174 def __add__(self,other):
164 """S + S2 -> New Struct made form S and S.merge(S2)"""
175 """S + S2 -> New Struct made form S and S.merge(S2)"""
165 Sout = self.copy()
176 Sout = self.copy()
166 Sout.merge(other)
177 Sout.merge(other)
167 return Sout
178 return Sout
168
179
169 def __sub__(self,other):
180 def __sub__(self,other):
170 """Return S1-S2, where all keys in S2 have been deleted (if present)
181 """Return S1-S2, where all keys in S2 have been deleted (if present)
171 from S1."""
182 from S1."""
172 Sout = self.copy()
183 Sout = self.copy()
173 Sout -= other
184 Sout -= other
174 return Sout
185 return Sout
175
186
176 def __isub__(self,other):
187 def __isub__(self,other):
177 """Do in place S = S - S2, meaning all keys in S2 have been deleted
188 """Do in place S = S - S2, meaning all keys in S2 have been deleted
178 (if present) from S1."""
189 (if present) from S1."""
179
190
180 for k in other.keys():
191 for k in other.keys():
181 if self.has_key(k):
192 if self.has_key(k):
182 del self.__dict__[k]
193 del self.__dict__[k]
183
194
184 def __make_dict(self,__loc_data__,**kw):
195 def __make_dict(self,__loc_data__,**kw):
185 "Helper function for update and merge. Return a dict from data."
196 "Helper function for update and merge. Return a dict from data."
186
197
187 if __loc_data__ == None:
198 if __loc_data__ == None:
188 dict = {}
199 dict = {}
189 elif type(__loc_data__) is types.DictType:
200 elif type(__loc_data__) is types.DictType:
190 dict = __loc_data__
201 dict = __loc_data__
191 elif isinstance(__loc_data__,Struct):
202 elif isinstance(__loc_data__,Struct):
192 dict = __loc_data__.__dict__
203 dict = __loc_data__.__dict__
193 else:
204 else:
194 raise TypeError, 'Update with a dict, a Struct or key=val pairs.'
205 raise TypeError, 'Update with a dict, a Struct or key=val pairs.'
195 if kw:
206 if kw:
196 dict.update(kw)
207 dict.update(kw)
197 return dict
208 return dict
198
209
199 def __dict_invert(self,dict):
210 def __dict_invert(self,dict):
200 """Helper function for merge. Takes a dictionary whose values are
211 """Helper function for merge. Takes a dictionary whose values are
201 lists and returns a dict. with the elements of each list as keys and
212 lists and returns a dict. with the elements of each list as keys and
202 the original keys as values."""
213 the original keys as values."""
203
214
204 outdict = {}
215 outdict = {}
205 for k,lst in dict.items():
216 for k,lst in dict.items():
206 if type(lst) is types.StringType:
217 if type(lst) is types.StringType:
207 lst = lst.split()
218 lst = lst.split()
208 for entry in lst:
219 for entry in lst:
209 outdict[entry] = k
220 outdict[entry] = k
210 return outdict
221 return outdict
211
222
212 def clear(self):
223 def clear(self):
213 """Clear all attributes."""
224 """Clear all attributes."""
214 self.__dict__.clear()
225 self.__dict__.clear()
215
226
216 def copy(self):
227 def copy(self):
217 """Return a (shallow) copy of a Struct."""
228 """Return a (shallow) copy of a Struct."""
218 return Struct(self.__dict__.copy())
229 return Struct(self.__dict__.copy())
219
230
220 def dict(self):
231 def dict(self):
221 """Return the Struct's dictionary."""
232 """Return the Struct's dictionary."""
222 return self.__dict__
233 return self.__dict__
223
234
224 def dictcopy(self):
235 def dictcopy(self):
225 """Return a (shallow) copy of the Struct's dictionary."""
236 """Return a (shallow) copy of the Struct's dictionary."""
226 return self.__dict__.copy()
237 return self.__dict__.copy()
227
238
228 def popitem(self):
239 def popitem(self):
229 """S.popitem() -> (k, v), remove and return some (key, value) pair as
240 """S.popitem() -> (k, v), remove and return some (key, value) pair as
230 a 2-tuple; but raise KeyError if S is empty."""
241 a 2-tuple; but raise KeyError if S is empty."""
231 return self.__dict__.popitem()
242 return self.__dict__.popitem()
232
243
233 def update(self,__loc_data__=None,**kw):
244 def update(self,__loc_data__=None,**kw):
234 """Update (merge) with data from another Struct or from a dictionary.
245 """Update (merge) with data from another Struct or from a dictionary.
235 Optionally, one or more key=value pairs can be given at the end for
246 Optionally, one or more key=value pairs can be given at the end for
236 direct update."""
247 direct update."""
237
248
238 # The funny name __loc_data__ is to prevent a common variable name which
249 # The funny name __loc_data__ is to prevent a common variable name which
239 # could be a fieled of a Struct to collide with this parameter. The problem
250 # could be a fieled of a Struct to collide with this parameter. The problem
240 # would arise if the function is called with a keyword with this same name
251 # would arise if the function is called with a keyword with this same name
241 # that a user means to add as a Struct field.
252 # that a user means to add as a Struct field.
242 newdict = Struct.__make_dict(self,__loc_data__,**kw)
253 newdict = Struct.__make_dict(self,__loc_data__,**kw)
243 for k,v in newdict.items():
254 for k,v in newdict.items():
244 self[k] = v
255 self[k] = v
245
256
246 def merge(self,__loc_data__=None,__conflict_solve=None,**kw):
257 def merge(self,__loc_data__=None,__conflict_solve=None,**kw):
247 """S.merge(data,conflict,k=v1,k=v2,...) -> merge data and k=v into S.
258 """S.merge(data,conflict,k=v1,k=v2,...) -> merge data and k=v into S.
248
259
249 This is similar to update(), but much more flexible. First, a dict is
260 This is similar to update(), but much more flexible. First, a dict is
250 made from data+key=value pairs. When merging this dict with the Struct
261 made from data+key=value pairs. When merging this dict with the Struct
251 S, the optional dictionary 'conflict' is used to decide what to do.
262 S, the optional dictionary 'conflict' is used to decide what to do.
252
263
253 If conflict is not given, the default behavior is to preserve any keys
264 If conflict is not given, the default behavior is to preserve any keys
254 with their current value (the opposite of the update method's
265 with their current value (the opposite of the update method's
255 behavior).
266 behavior).
256
267
257 conflict is a dictionary of binary functions which will be used to
268 conflict is a dictionary of binary functions which will be used to
258 solve key conflicts. It must have the following structure:
269 solve key conflicts. It must have the following structure:
259
270
260 conflict == { fn1 : [Skey1,Skey2,...], fn2 : [Skey3], etc }
271 conflict == { fn1 : [Skey1,Skey2,...], fn2 : [Skey3], etc }
261
272
262 Values must be lists or whitespace separated strings which are
273 Values must be lists or whitespace separated strings which are
263 automatically converted to lists of strings by calling string.split().
274 automatically converted to lists of strings by calling string.split().
264
275
265 Each key of conflict is a function which defines a policy for
276 Each key of conflict is a function which defines a policy for
266 resolving conflicts when merging with the input data. Each fn must be
277 resolving conflicts when merging with the input data. Each fn must be
267 a binary function which returns the desired outcome for a key
278 a binary function which returns the desired outcome for a key
268 conflict. These functions will be called as fn(old,new).
279 conflict. These functions will be called as fn(old,new).
269
280
270 An example is probably in order. Suppose you are merging the struct S
281 An example is probably in order. Suppose you are merging the struct S
271 with a dict D and the following conflict policy dict:
282 with a dict D and the following conflict policy dict:
272
283
273 S.merge(D,{fn1:['a','b',4], fn2:'key_c key_d'})
284 S.merge(D,{fn1:['a','b',4], fn2:'key_c key_d'})
274
285
275 If the key 'a' is found in both S and D, the merge method will call:
286 If the key 'a' is found in both S and D, the merge method will call:
276
287
277 S['a'] = fn1(S['a'],D['a'])
288 S['a'] = fn1(S['a'],D['a'])
278
289
279 As a convenience, merge() provides five (the most commonly needed)
290 As a convenience, merge() provides five (the most commonly needed)
280 pre-defined policies: preserve, update, add, add_flip and add_s. The
291 pre-defined policies: preserve, update, add, add_flip and add_s. The
281 easiest explanation is their implementation:
292 easiest explanation is their implementation:
282
293
283 preserve = lambda old,new: old
294 preserve = lambda old,new: old
284 update = lambda old,new: new
295 update = lambda old,new: new
285 add = lambda old,new: old + new
296 add = lambda old,new: old + new
286 add_flip = lambda old,new: new + old # note change of order!
297 add_flip = lambda old,new: new + old # note change of order!
287 add_s = lambda old,new: old + ' ' + new # only works for strings!
298 add_s = lambda old,new: old + ' ' + new # only works for strings!
288
299
289 You can use those four words (as strings) as keys in conflict instead
300 You can use those four words (as strings) as keys in conflict instead
290 of defining them as functions, and the merge method will substitute
301 of defining them as functions, and the merge method will substitute
291 the appropriate functions for you. That is, the call
302 the appropriate functions for you. That is, the call
292
303
293 S.merge(D,{'preserve':'a b c','add':[4,5,'d'],my_function:[6]})
304 S.merge(D,{'preserve':'a b c','add':[4,5,'d'],my_function:[6]})
294
305
295 will automatically substitute the functions preserve and add for the
306 will automatically substitute the functions preserve and add for the
296 names 'preserve' and 'add' before making any function calls.
307 names 'preserve' and 'add' before making any function calls.
297
308
298 For more complicated conflict resolution policies, you still need to
309 For more complicated conflict resolution policies, you still need to
299 construct your own functions. """
310 construct your own functions. """
300
311
301 data_dict = Struct.__make_dict(self,__loc_data__,**kw)
312 data_dict = Struct.__make_dict(self,__loc_data__,**kw)
302
313
303 # policies for conflict resolution: two argument functions which return
314 # policies for conflict resolution: two argument functions which return
304 # the value that will go in the new struct
315 # the value that will go in the new struct
305 preserve = lambda old,new: old
316 preserve = lambda old,new: old
306 update = lambda old,new: new
317 update = lambda old,new: new
307 add = lambda old,new: old + new
318 add = lambda old,new: old + new
308 add_flip = lambda old,new: new + old # note change of order!
319 add_flip = lambda old,new: new + old # note change of order!
309 add_s = lambda old,new: old + ' ' + new
320 add_s = lambda old,new: old + ' ' + new
310
321
311 # default policy is to keep current keys when there's a conflict
322 # default policy is to keep current keys when there's a conflict
312 conflict_solve = list2dict2(self.keys(),default = preserve)
323 conflict_solve = list2dict2(self.keys(),default = preserve)
313
324
314 # the conflict_solve dictionary is given by the user 'inverted': we
325 # the conflict_solve dictionary is given by the user 'inverted': we
315 # need a name-function mapping, it comes as a function -> names
326 # need a name-function mapping, it comes as a function -> names
316 # dict. Make a local copy (b/c we'll make changes), replace user
327 # dict. Make a local copy (b/c we'll make changes), replace user
317 # strings for the three builtin policies and invert it.
328 # strings for the three builtin policies and invert it.
318 if __conflict_solve:
329 if __conflict_solve:
319 inv_conflict_solve_user = __conflict_solve.copy()
330 inv_conflict_solve_user = __conflict_solve.copy()
320 for name, func in [('preserve',preserve), ('update',update),
331 for name, func in [('preserve',preserve), ('update',update),
321 ('add',add), ('add_flip',add_flip), ('add_s',add_s)]:
332 ('add',add), ('add_flip',add_flip),
333 ('add_s',add_s)]:
322 if name in inv_conflict_solve_user.keys():
334 if name in inv_conflict_solve_user.keys():
323 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
335 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
324 del inv_conflict_solve_user[name]
336 del inv_conflict_solve_user[name]
325 conflict_solve.update(Struct.__dict_invert(self,inv_conflict_solve_user))
337 conflict_solve.update(Struct.__dict_invert(self,inv_conflict_solve_user))
326 #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg
338 #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg
327 #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve)
339 #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve)
328 for key in data_dict:
340 for key in data_dict:
329 if key not in self:
341 if key not in self:
330 self[key] = data_dict[key]
342 self[key] = data_dict[key]
331 else:
343 else:
332 self[key] = conflict_solve[key](self[key],data_dict[key])
344 self[key] = conflict_solve[key](self[key],data_dict[key])
333
345
334 def has_key(self,key):
346 def has_key(self,key):
335 """Like has_key() dictionary method."""
347 """Like has_key() dictionary method."""
336 return self.__dict__.has_key(key)
348 return self.__dict__.has_key(key)
337
349
338 def hasattr(self,key):
350 def hasattr(self,key):
339 """hasattr function available as a method.
351 """hasattr function available as a method.
340
352
341 Implemented like has_key, to make sure that all available keys in the
353 Implemented like has_key, to make sure that all available keys in the
342 internal dictionary of the Struct appear also as attributes (even
354 internal dictionary of the Struct appear also as attributes (even
343 numeric keys)."""
355 numeric keys)."""
344 return self.__dict__.has_key(key)
356 return self.__dict__.has_key(key)
345
357
346 def items(self):
358 def items(self):
347 """Return the items in the Struct's dictionary, in the same format
359 """Return the items in the Struct's dictionary, in the same format
348 as a call to {}.items()."""
360 as a call to {}.items()."""
349 return self.__dict__.items()
361 return self.__dict__.items()
350
362
351 def keys(self):
363 def keys(self):
352 """Return the keys in the Struct's dictionary, in the same format
364 """Return the keys in the Struct's dictionary, in the same format
353 as a call to {}.keys()."""
365 as a call to {}.keys()."""
354 return self.__dict__.keys()
366 return self.__dict__.keys()
355
367
356 def values(self,keys=None):
368 def values(self,keys=None):
357 """Return the values in the Struct's dictionary, in the same format
369 """Return the values in the Struct's dictionary, in the same format
358 as a call to {}.values().
370 as a call to {}.values().
359
371
360 Can be called with an optional argument keys, which must be a list or
372 Can be called with an optional argument keys, which must be a list or
361 tuple of keys. In this case it returns only the values corresponding
373 tuple of keys. In this case it returns only the values corresponding
362 to those keys (allowing a form of 'slicing' for Structs)."""
374 to those keys (allowing a form of 'slicing' for Structs)."""
363 if not keys:
375 if not keys:
364 return self.__dict__.values()
376 return self.__dict__.values()
365 else:
377 else:
366 ret=[]
378 ret=[]
367 for k in keys:
379 for k in keys:
368 ret.append(self[k])
380 ret.append(self[k])
369 return ret
381 return ret
370
382
371 def get(self,attr,val=None):
383 def get(self,attr,val=None):
372 """S.get(k[,d]) -> S[k] if S.has_key(k), else d. d defaults to None."""
384 """S.get(k[,d]) -> S[k] if k in S, else d. d defaults to None."""
373 try:
385 try:
374 return self[attr]
386 return self[attr]
375 except KeyError:
387 except KeyError:
376 return val
388 return val
377
389
378 def setdefault(self,attr,val=None):
390 def setdefault(self,attr,val=None):
379 """S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if not S.has_key(k)"""
391 """S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if k not in S"""
380 if not self.has_key(attr):
392 if not self.has_key(attr):
381 self[attr] = val
393 self[attr] = val
382 return self.get(attr,val)
394 return self.get(attr,val)
383
395
384 def allow_new_attr(self, allow = True):
396 def allow_new_attr(self, allow = True):
385 """ Set whether new attributes can be created inside struct
397 """ Set whether new attributes can be created inside struct
386
398
387 This can be used to catch typos by verifying that the attribute user tries to
399 This can be used to catch typos by verifying that the attribute user
388 change already exists in this Struct.
400 tries to change already exists in this Struct.
389 """
401 """
390 self['__allownew'] = allow
402 self['__allownew'] = allow
391
403
392
404
393 # end class Struct
405 # end class Struct
394
406
@@ -1,874 +1,876 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.tests.test_engineservice -*-
2 # -*- test-case-name: IPython.kernel.tests.test_engineservice -*-
3
3
4 """A Twisted Service Representation of the IPython core.
4 """A Twisted Service Representation of the IPython core.
5
5
6 The IPython Core exposed to the network is called the Engine. Its
6 The IPython Core exposed to the network is called the Engine. Its
7 representation in Twisted in the EngineService. Interfaces and adapters
7 representation in Twisted in the EngineService. Interfaces and adapters
8 are used to abstract out the details of the actual network protocol used.
8 are used to abstract out the details of the actual network protocol used.
9 The EngineService is an Engine that knows nothing about the actual protocol
9 The EngineService is an Engine that knows nothing about the actual protocol
10 used.
10 used.
11
11
12 The EngineService is exposed with various network protocols in modules like:
12 The EngineService is exposed with various network protocols in modules like:
13
13
14 enginepb.py
14 enginepb.py
15 enginevanilla.py
15 enginevanilla.py
16
16
17 As of 12/12/06 the classes in this module have been simplified greatly. It was
17 As of 12/12/06 the classes in this module have been simplified greatly. It was
18 felt that we had over-engineered things. To improve the maintainability of the
18 felt that we had over-engineered things. To improve the maintainability of the
19 code we have taken out the ICompleteEngine interface and the completeEngine
19 code we have taken out the ICompleteEngine interface and the completeEngine
20 method that automatically added methods to engines.
20 method that automatically added methods to engines.
21
21
22 """
22 """
23
23
24 __docformat__ = "restructuredtext en"
24 __docformat__ = "restructuredtext en"
25
25
26 #-------------------------------------------------------------------------------
26 #-------------------------------------------------------------------------------
27 # Copyright (C) 2008 The IPython Development Team
27 # Copyright (C) 2008 The IPython Development Team
28 #
28 #
29 # Distributed under the terms of the BSD License. The full license is in
29 # Distributed under the terms of the BSD License. The full license is in
30 # the file COPYING, distributed as part of this software.
30 # the file COPYING, distributed as part of this software.
31 #-------------------------------------------------------------------------------
31 #-------------------------------------------------------------------------------
32
32
33 #-------------------------------------------------------------------------------
33 #-------------------------------------------------------------------------------
34 # Imports
34 # Imports
35 #-------------------------------------------------------------------------------
35 #-------------------------------------------------------------------------------
36
36
37 import os, sys, copy
37 import os, sys, copy
38 import cPickle as pickle
38 import cPickle as pickle
39 from new import instancemethod
39 from new import instancemethod
40
40
41 from twisted.application import service
41 from twisted.application import service
42 from twisted.internet import defer, reactor
42 from twisted.internet import defer, reactor
43 from twisted.python import log, failure, components
43 from twisted.python import log, failure, components
44 import zope.interface as zi
44 import zope.interface as zi
45
45
46 from IPython.kernel.core.interpreter import Interpreter
46 from IPython.kernel.core.interpreter import Interpreter
47 from IPython.kernel import newserialized, error, util
47 from IPython.kernel import newserialized, error, util
48 from IPython.kernel.util import printer
48 from IPython.kernel.util import printer
49 from IPython.kernel.twistedutil import gatherBoth, DeferredList
49 from IPython.kernel.twistedutil import gatherBoth, DeferredList
50 from IPython.kernel import codeutil
50 from IPython.kernel import codeutil
51
51
52
52
53 #-------------------------------------------------------------------------------
53 #-------------------------------------------------------------------------------
54 # Interface specification for the Engine
54 # Interface specification for the Engine
55 #-------------------------------------------------------------------------------
55 #-------------------------------------------------------------------------------
56
56
57 class IEngineCore(zi.Interface):
57 class IEngineCore(zi.Interface):
58 """The minimal required interface for the IPython Engine.
58 """The minimal required interface for the IPython Engine.
59
59
60 This interface provides a formal specification of the IPython core.
60 This interface provides a formal specification of the IPython core.
61 All these methods should return deferreds regardless of what side of a
61 All these methods should return deferreds regardless of what side of a
62 network connection they are on.
62 network connection they are on.
63
63
64 In general, this class simply wraps a shell class and wraps its return
64 In general, this class simply wraps a shell class and wraps its return
65 values as Deferred objects. If the underlying shell class method raises
65 values as Deferred objects. If the underlying shell class method raises
66 an exception, this class should convert it to a twisted.failure.Failure
66 an exception, this class should convert it to a twisted.failure.Failure
67 that will be propagated along the Deferred's errback chain.
67 that will be propagated along the Deferred's errback chain.
68
68
69 In addition, Failures are aggressive. By this, we mean that if a method
69 In addition, Failures are aggressive. By this, we mean that if a method
70 is performing multiple actions (like pulling multiple object) if any
70 is performing multiple actions (like pulling multiple object) if any
71 single one fails, the entire method will fail with that Failure. It is
71 single one fails, the entire method will fail with that Failure. It is
72 all or nothing.
72 all or nothing.
73 """
73 """
74
74
75 id = zi.interface.Attribute("the id of the Engine object")
75 id = zi.interface.Attribute("the id of the Engine object")
76 properties = zi.interface.Attribute("A dict of properties of the Engine")
76 properties = zi.interface.Attribute("A dict of properties of the Engine")
77
77
78 def execute(lines):
78 def execute(lines):
79 """Execute lines of Python code.
79 """Execute lines of Python code.
80
80
81 Returns a dictionary with keys (id, number, stdin, stdout, stderr)
81 Returns a dictionary with keys (id, number, stdin, stdout, stderr)
82 upon success.
82 upon success.
83
83
84 Returns a failure object if the execution of lines raises an exception.
84 Returns a failure object if the execution of lines raises an exception.
85 """
85 """
86
86
87 def push(namespace):
87 def push(namespace):
88 """Push dict namespace into the user's namespace.
88 """Push dict namespace into the user's namespace.
89
89
90 Returns a deferred to None or a failure.
90 Returns a deferred to None or a failure.
91 """
91 """
92
92
93 def pull(keys):
93 def pull(keys):
94 """Pulls values out of the user's namespace by keys.
94 """Pulls values out of the user's namespace by keys.
95
95
96 Returns a deferred to a tuple objects or a single object.
96 Returns a deferred to a tuple objects or a single object.
97
97
98 Raises NameError if any one of objects doess not exist.
98 Raises NameError if any one of objects doess not exist.
99 """
99 """
100
100
101 def push_function(namespace):
101 def push_function(namespace):
102 """Push a dict of key, function pairs into the user's namespace.
102 """Push a dict of key, function pairs into the user's namespace.
103
103
104 Returns a deferred to None or a failure."""
104 Returns a deferred to None or a failure."""
105
105
106 def pull_function(keys):
106 def pull_function(keys):
107 """Pulls functions out of the user's namespace by keys.
107 """Pulls functions out of the user's namespace by keys.
108
108
109 Returns a deferred to a tuple of functions or a single function.
109 Returns a deferred to a tuple of functions or a single function.
110
110
111 Raises NameError if any one of the functions does not exist.
111 Raises NameError if any one of the functions does not exist.
112 """
112 """
113
113
114 def get_result(i=None):
114 def get_result(i=None):
115 """Get the stdin/stdout/stderr of command i.
115 """Get the stdin/stdout/stderr of command i.
116
116
117 Returns a deferred to a dict with keys
117 Returns a deferred to a dict with keys
118 (id, number, stdin, stdout, stderr).
118 (id, number, stdin, stdout, stderr).
119
119
120 Raises IndexError if command i does not exist.
120 Raises IndexError if command i does not exist.
121 Raises TypeError if i in not an int.
121 Raises TypeError if i in not an int.
122 """
122 """
123
123
124 def reset():
124 def reset():
125 """Reset the shell.
125 """Reset the shell.
126
126
127 This clears the users namespace. Won't cause modules to be
127 This clears the users namespace. Won't cause modules to be
128 reloaded. Should also re-initialize certain variables like id.
128 reloaded. Should also re-initialize certain variables like id.
129 """
129 """
130
130
131 def kill():
131 def kill():
132 """Kill the engine by stopping the reactor."""
132 """Kill the engine by stopping the reactor."""
133
133
134 def keys():
134 def keys():
135 """Return the top level variables in the users namspace.
135 """Return the top level variables in the users namspace.
136
136
137 Returns a deferred to a dict."""
137 Returns a deferred to a dict."""
138
138
139
139
140 class IEngineSerialized(zi.Interface):
140 class IEngineSerialized(zi.Interface):
141 """Push/Pull methods that take Serialized objects.
141 """Push/Pull methods that take Serialized objects.
142
142
143 All methods should return deferreds.
143 All methods should return deferreds.
144 """
144 """
145
145
146 def push_serialized(namespace):
146 def push_serialized(namespace):
147 """Push a dict of keys and Serialized objects into the user's namespace."""
147 """Push a dict of keys and Serialized objects into the user's namespace."""
148
148
149 def pull_serialized(keys):
149 def pull_serialized(keys):
150 """Pull objects by key from the user's namespace as Serialized.
150 """Pull objects by key from the user's namespace as Serialized.
151
151
152 Returns a list of or one Serialized.
152 Returns a list of or one Serialized.
153
153
154 Raises NameError is any one of the objects does not exist.
154 Raises NameError is any one of the objects does not exist.
155 """
155 """
156
156
157
157
158 class IEngineProperties(zi.Interface):
158 class IEngineProperties(zi.Interface):
159 """Methods for access to the properties object of an Engine"""
159 """Methods for access to the properties object of an Engine"""
160
160
161 properties = zi.Attribute("A StrictDict object, containing the properties")
161 properties = zi.Attribute("A StrictDict object, containing the properties")
162
162
163 def set_properties(properties):
163 def set_properties(properties):
164 """set properties by key and value"""
164 """set properties by key and value"""
165
165
166 def get_properties(keys=None):
166 def get_properties(keys=None):
167 """get a list of properties by `keys`, if no keys specified, get all"""
167 """get a list of properties by `keys`, if no keys specified, get all"""
168
168
169 def del_properties(keys):
169 def del_properties(keys):
170 """delete properties by `keys`"""
170 """delete properties by `keys`"""
171
171
172 def has_properties(keys):
172 def has_properties(keys):
173 """get a list of bool values for whether `properties` has `keys`"""
173 """get a list of bool values for whether `properties` has `keys`"""
174
174
175 def clear_properties():
175 def clear_properties():
176 """clear the properties dict"""
176 """clear the properties dict"""
177
177
178 class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties):
178 class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties):
179 """The basic engine interface that EngineService will implement.
179 """The basic engine interface that EngineService will implement.
180
180
181 This exists so it is easy to specify adapters that adapt to and from the
181 This exists so it is easy to specify adapters that adapt to and from the
182 API that the basic EngineService implements.
182 API that the basic EngineService implements.
183 """
183 """
184 pass
184 pass
185
185
186 class IEngineQueued(IEngineBase):
186 class IEngineQueued(IEngineBase):
187 """Interface for adding a queue to an IEngineBase.
187 """Interface for adding a queue to an IEngineBase.
188
188
189 This interface extends the IEngineBase interface to add methods for managing
189 This interface extends the IEngineBase interface to add methods for managing
190 the engine's queue. The implicit details of this interface are that the
190 the engine's queue. The implicit details of this interface are that the
191 execution of all methods declared in IEngineBase should appropriately be
191 execution of all methods declared in IEngineBase should appropriately be
192 put through a queue before execution.
192 put through a queue before execution.
193
193
194 All methods should return deferreds.
194 All methods should return deferreds.
195 """
195 """
196
196
197 def clear_queue():
197 def clear_queue():
198 """Clear the queue."""
198 """Clear the queue."""
199
199
200 def queue_status():
200 def queue_status():
201 """Get the queued and pending commands in the queue."""
201 """Get the queued and pending commands in the queue."""
202
202
203 def register_failure_observer(obs):
203 def register_failure_observer(obs):
204 """Register an observer of pending Failures.
204 """Register an observer of pending Failures.
205
205
206 The observer must implement IFailureObserver.
206 The observer must implement IFailureObserver.
207 """
207 """
208
208
209 def unregister_failure_observer(obs):
209 def unregister_failure_observer(obs):
210 """Unregister an observer of pending Failures."""
210 """Unregister an observer of pending Failures."""
211
211
212
212
213 class IEngineThreaded(zi.Interface):
213 class IEngineThreaded(zi.Interface):
214 """A place holder for threaded commands.
214 """A place holder for threaded commands.
215
215
216 All methods should return deferreds.
216 All methods should return deferreds.
217 """
217 """
218 pass
218 pass
219
219
220
220
221 #-------------------------------------------------------------------------------
221 #-------------------------------------------------------------------------------
222 # Functions and classes to implement the EngineService
222 # Functions and classes to implement the EngineService
223 #-------------------------------------------------------------------------------
223 #-------------------------------------------------------------------------------
224
224
225
225
226 class StrictDict(dict):
226 class StrictDict(dict):
227 """This is a strict copying dictionary for use as the interface to the
227 """This is a strict copying dictionary for use as the interface to the
228 properties of an Engine.
228 properties of an Engine.
229 :IMPORTANT:
229 :IMPORTANT:
230 This object copies the values you set to it, and returns copies to you
230 This object copies the values you set to it, and returns copies to you
231 when you request them. The only way to change properties os explicitly
231 when you request them. The only way to change properties os explicitly
232 through the setitem and getitem of the dictionary interface.
232 through the setitem and getitem of the dictionary interface.
233 Example:
233 Example:
234 >>> e = kernel.get_engine(id)
234 >>> e = kernel.get_engine(id)
235 >>> L = someList
235 >>> L = someList
236 >>> e.properties['L'] = L
236 >>> e.properties['L'] = L
237 >>> L == e.properties['L']
237 >>> L == e.properties['L']
238 ... True
238 ... True
239 >>> L.append(something Else)
239 >>> L.append(something Else)
240 >>> L == e.properties['L']
240 >>> L == e.properties['L']
241 ... False
241 ... False
242
242
243 getitem copies, so calls to methods of objects do not affect the
243 getitem copies, so calls to methods of objects do not affect the
244 properties, as in the following example:
244 properties, as in the following example:
245 >>> e.properties[1] = range(2)
245 >>> e.properties[1] = range(2)
246 >>> print e.properties[1]
246 >>> print e.properties[1]
247 ... [0, 1]
247 ... [0, 1]
248 >>> e.properties[1].append(2)
248 >>> e.properties[1].append(2)
249 >>> print e.properties[1]
249 >>> print e.properties[1]
250 ... [0, 1]
250 ... [0, 1]
251
251
252 """
252 """
253 def __init__(self, *args, **kwargs):
253 def __init__(self, *args, **kwargs):
254 dict.__init__(self, *args, **kwargs)
254 dict.__init__(self, *args, **kwargs)
255 self.modified = True
255 self.modified = True
256
256
257 def __getitem__(self, key):
257 def __getitem__(self, key):
258 return copy.deepcopy(dict.__getitem__(self, key))
258 return copy.deepcopy(dict.__getitem__(self, key))
259
259
260 def __setitem__(self, key, value):
260 def __setitem__(self, key, value):
261 # check if this entry is valid for transport around the network
261 # check if this entry is valid for transport around the network
262 # and copying
262 # and copying
263 try:
263 try:
264 pickle.dumps(key, 2)
264 pickle.dumps(key, 2)
265 pickle.dumps(value, 2)
265 pickle.dumps(value, 2)
266 newvalue = copy.deepcopy(value)
266 newvalue = copy.deepcopy(value)
267 except:
267 except:
268 raise error.InvalidProperty(value)
268 raise error.InvalidProperty(value)
269 dict.__setitem__(self, key, newvalue)
269 dict.__setitem__(self, key, newvalue)
270 self.modified = True
270 self.modified = True
271
271
272 def __delitem__(self, key):
272 def __delitem__(self, key):
273 dict.__delitem__(self, key)
273 dict.__delitem__(self, key)
274 self.modified = True
274 self.modified = True
275
275
276 def update(self, dikt):
276 def update(self, dikt):
277 for k,v in dikt.iteritems():
277 for k,v in dikt.iteritems():
278 self[k] = v
278 self[k] = v
279
279
280 def pop(self, key):
280 def pop(self, key):
281 self.modified = True
281 self.modified = True
282 return dict.pop(self, key)
282 return dict.pop(self, key)
283
283
284 def popitem(self):
284 def popitem(self):
285 self.modified = True
285 self.modified = True
286 return dict.popitem(self)
286 return dict.popitem(self)
287
287
288 def clear(self):
288 def clear(self):
289 self.modified = True
289 self.modified = True
290 dict.clear(self)
290 dict.clear(self)
291
291
292 def subDict(self, *keys):
292 def subDict(self, *keys):
293 d = {}
293 d = {}
294 for key in keys:
294 for key in keys:
295 d[key] = self[key]
295 d[key] = self[key]
296 return d
296 return d
297
297
298
298
299
299
300 class EngineAPI(object):
300 class EngineAPI(object):
301 """This is the object through which the user can edit the `properties`
301 """This is the object through which the user can edit the `properties`
302 attribute of an Engine.
302 attribute of an Engine.
303 The Engine Properties object copies all object in and out of itself.
303 The Engine Properties object copies all object in and out of itself.
304 See the EngineProperties object for details.
304 See the EngineProperties object for details.
305 """
305 """
306 _fix=False
306 _fix=False
307 def __init__(self, id):
307 def __init__(self, id):
308 self.id = id
308 self.id = id
309 self.properties = StrictDict()
309 self.properties = StrictDict()
310 self._fix=True
310 self._fix=True
311
311
312 def __setattr__(self, k,v):
312 def __setattr__(self, k,v):
313 if self._fix:
313 if self._fix:
314 raise error.KernelError("I am protected!")
314 raise error.KernelError("I am protected!")
315 else:
315 else:
316 object.__setattr__(self, k, v)
316 object.__setattr__(self, k, v)
317
317
318 def __delattr__(self, key):
318 def __delattr__(self, key):
319 raise error.KernelError("I am protected!")
319 raise error.KernelError("I am protected!")
320
320
321
321
322 _apiDict = {}
322 _apiDict = {}
323
323
324 def get_engine(id):
324 def get_engine(id):
325 """Get the Engine API object, whcih currently just provides the properties
325 """Get the Engine API object, whcih currently just provides the properties
326 object, by ID"""
326 object, by ID"""
327 global _apiDict
327 global _apiDict
328 if not _apiDict.get(id):
328 if not _apiDict.get(id):
329 _apiDict[id] = EngineAPI(id)
329 _apiDict[id] = EngineAPI(id)
330 return _apiDict[id]
330 return _apiDict[id]
331
331
332 def drop_engine(id):
332 def drop_engine(id):
333 """remove an engine"""
333 """remove an engine"""
334 global _apiDict
334 global _apiDict
335 if _apiDict.has_key(id):
335 if _apiDict.has_key(id):
336 del _apiDict[id]
336 del _apiDict[id]
337
337
338 class EngineService(object, service.Service):
338 class EngineService(object, service.Service):
339 """Adapt a IPython shell into a IEngine implementing Twisted Service."""
339 """Adapt a IPython shell into a IEngine implementing Twisted Service."""
340
340
341 zi.implements(IEngineBase)
341 zi.implements(IEngineBase)
342 name = 'EngineService'
342 name = 'EngineService'
343
343
344 def __init__(self, shellClass=Interpreter, mpi=None):
344 def __init__(self, shellClass=Interpreter, mpi=None):
345 """Create an EngineService.
345 """Create an EngineService.
346
346
347 shellClass: something that implements IInterpreter or core1
347 shellClass: something that implements IInterpreter or core1
348 mpi: an mpi module that has rank and size attributes
348 mpi: an mpi module that has rank and size attributes
349 """
349 """
350 self.shellClass = shellClass
350 self.shellClass = shellClass
351 self.shell = self.shellClass()
351 self.shell = self.shellClass()
352 self.mpi = mpi
352 self.mpi = mpi
353 self.id = None
353 self.id = None
354 self.properties = get_engine(self.id).properties
354 self.properties = get_engine(self.id).properties
355 if self.mpi is not None:
355 if self.mpi is not None:
356 log.msg("MPI started with rank = %i and size = %i" %
356 log.msg("MPI started with rank = %i and size = %i" %
357 (self.mpi.rank, self.mpi.size))
357 (self.mpi.rank, self.mpi.size))
358 self.id = self.mpi.rank
358 self.id = self.mpi.rank
359 self._seedNamespace()
359 self._seedNamespace()
360
360
361 # Make id a property so that the shell can get the updated id
361 # Make id a property so that the shell can get the updated id
362
362
363 def _setID(self, id):
363 def _setID(self, id):
364 self._id = id
364 self._id = id
365 self.properties = get_engine(id).properties
365 self.properties = get_engine(id).properties
366 self.shell.push({'id': id})
366 self.shell.push({'id': id})
367
367
368 def _getID(self):
368 def _getID(self):
369 return self._id
369 return self._id
370
370
371 id = property(_getID, _setID)
371 id = property(_getID, _setID)
372
372
373 def _seedNamespace(self):
373 def _seedNamespace(self):
374 self.shell.push({'mpi': self.mpi, 'id' : self.id})
374 self.shell.push({'mpi': self.mpi, 'id' : self.id})
375
375
376 def executeAndRaise(self, msg, callable, *args, **kwargs):
376 def executeAndRaise(self, msg, callable, *args, **kwargs):
377 """Call a method of self.shell and wrap any exception."""
377 """Call a method of self.shell and wrap any exception."""
378 d = defer.Deferred()
378 d = defer.Deferred()
379 try:
379 try:
380 result = callable(*args, **kwargs)
380 result = callable(*args, **kwargs)
381 except:
381 except:
382 # This gives the following:
382 # This gives the following:
383 # et=exception class
383 # et=exception class
384 # ev=exception class instance
384 # ev=exception class instance
385 # tb=traceback object
385 # tb=traceback object
386 et,ev,tb = sys.exc_info()
386 et,ev,tb = sys.exc_info()
387 # This call adds attributes to the exception value
387 # This call adds attributes to the exception value
388 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
388 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
389 # Add another attribute
389 # Add another attribute
390 ev._ipython_engine_info = msg
390 ev._ipython_engine_info = msg
391 f = failure.Failure(ev,et,None)
391 f = failure.Failure(ev,et,None)
392 d.errback(f)
392 d.errback(f)
393 else:
393 else:
394 d.callback(result)
394 d.callback(result)
395
395
396 return d
396 return d
397
397
398 # The IEngine methods. See the interface for documentation.
398 # The IEngine methods. See the interface for documentation.
399
399
400 def execute(self, lines):
400 def execute(self, lines):
401 msg = {'engineid':self.id,
401 msg = {'engineid':self.id,
402 'method':'execute',
402 'method':'execute',
403 'args':[lines]}
403 'args':[lines]}
404 d = self.executeAndRaise(msg, self.shell.execute, lines)
404 d = self.executeAndRaise(msg, self.shell.execute, lines)
405 d.addCallback(self.addIDToResult)
405 d.addCallback(self.addIDToResult)
406 return d
406 return d
407
407
408 def addIDToResult(self, result):
408 def addIDToResult(self, result):
409 result['id'] = self.id
409 result['id'] = self.id
410 return result
410 return result
411
411
412 def push(self, namespace):
412 def push(self, namespace):
413 msg = {'engineid':self.id,
413 msg = {'engineid':self.id,
414 'method':'push',
414 'method':'push',
415 'args':[repr(namespace.keys())]}
415 'args':[repr(namespace.keys())]}
416 d = self.executeAndRaise(msg, self.shell.push, namespace)
416 d = self.executeAndRaise(msg, self.shell.push, namespace)
417 return d
417 return d
418
418
419 def pull(self, keys):
419 def pull(self, keys):
420 msg = {'engineid':self.id,
420 msg = {'engineid':self.id,
421 'method':'pull',
421 'method':'pull',
422 'args':[repr(keys)]}
422 'args':[repr(keys)]}
423 d = self.executeAndRaise(msg, self.shell.pull, keys)
423 d = self.executeAndRaise(msg, self.shell.pull, keys)
424 return d
424 return d
425
425
426 def push_function(self, namespace):
426 def push_function(self, namespace):
427 msg = {'engineid':self.id,
427 msg = {'engineid':self.id,
428 'method':'push_function',
428 'method':'push_function',
429 'args':[repr(namespace.keys())]}
429 'args':[repr(namespace.keys())]}
430 d = self.executeAndRaise(msg, self.shell.push_function, namespace)
430 d = self.executeAndRaise(msg, self.shell.push_function, namespace)
431 return d
431 return d
432
432
433 def pull_function(self, keys):
433 def pull_function(self, keys):
434 msg = {'engineid':self.id,
434 msg = {'engineid':self.id,
435 'method':'pull_function',
435 'method':'pull_function',
436 'args':[repr(keys)]}
436 'args':[repr(keys)]}
437 d = self.executeAndRaise(msg, self.shell.pull_function, keys)
437 d = self.executeAndRaise(msg, self.shell.pull_function, keys)
438 return d
438 return d
439
439
440 def get_result(self, i=None):
440 def get_result(self, i=None):
441 msg = {'engineid':self.id,
441 msg = {'engineid':self.id,
442 'method':'get_result',
442 'method':'get_result',
443 'args':[repr(i)]}
443 'args':[repr(i)]}
444 d = self.executeAndRaise(msg, self.shell.getCommand, i)
444 d = self.executeAndRaise(msg, self.shell.getCommand, i)
445 d.addCallback(self.addIDToResult)
445 d.addCallback(self.addIDToResult)
446 return d
446 return d
447
447
448 def reset(self):
448 def reset(self):
449 msg = {'engineid':self.id,
449 msg = {'engineid':self.id,
450 'method':'reset',
450 'method':'reset',
451 'args':[]}
451 'args':[]}
452 del self.shell
452 del self.shell
453 self.shell = self.shellClass()
453 self.shell = self.shellClass()
454 self.properties.clear()
454 self.properties.clear()
455 d = self.executeAndRaise(msg, self._seedNamespace)
455 d = self.executeAndRaise(msg, self._seedNamespace)
456 return d
456 return d
457
457
458 def kill(self):
458 def kill(self):
459 drop_engine(self.id)
459 drop_engine(self.id)
460 try:
460 try:
461 reactor.stop()
461 reactor.stop()
462 except RuntimeError:
462 except RuntimeError:
463 log.msg('The reactor was not running apparently.')
463 log.msg('The reactor was not running apparently.')
464 return defer.fail()
464 return defer.fail()
465 else:
465 else:
466 return defer.succeed(None)
466 return defer.succeed(None)
467
467
468 def keys(self):
468 def keys(self):
469 """Return a list of variables names in the users top level namespace.
469 """Return a list of variables names in the users top level namespace.
470
470
471 This used to return a dict of all the keys/repr(values) in the
471 This used to return a dict of all the keys/repr(values) in the
472 user's namespace. This was too much info for the ControllerService
472 user's namespace. This was too much info for the ControllerService
473 to handle so it is now just a list of keys.
473 to handle so it is now just a list of keys.
474 """
474 """
475
475
476 remotes = []
476 remotes = []
477 for k in self.shell.user_ns.iterkeys():
477 for k in self.shell.user_ns.iterkeys():
478 if k not in ['__name__', '_ih', '_oh', '__builtins__',
478 if k not in ['__name__', '_ih', '_oh', '__builtins__',
479 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']:
479 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']:
480 remotes.append(k)
480 remotes.append(k)
481 return defer.succeed(remotes)
481 return defer.succeed(remotes)
482
482
483 def set_properties(self, properties):
483 def set_properties(self, properties):
484 msg = {'engineid':self.id,
484 msg = {'engineid':self.id,
485 'method':'set_properties',
485 'method':'set_properties',
486 'args':[repr(properties.keys())]}
486 'args':[repr(properties.keys())]}
487 return self.executeAndRaise(msg, self.properties.update, properties)
487 return self.executeAndRaise(msg, self.properties.update, properties)
488
488
489 def get_properties(self, keys=None):
489 def get_properties(self, keys=None):
490 msg = {'engineid':self.id,
490 msg = {'engineid':self.id,
491 'method':'get_properties',
491 'method':'get_properties',
492 'args':[repr(keys)]}
492 'args':[repr(keys)]}
493 if keys is None:
493 if keys is None:
494 keys = self.properties.keys()
494 keys = self.properties.keys()
495 return self.executeAndRaise(msg, self.properties.subDict, *keys)
495 return self.executeAndRaise(msg, self.properties.subDict, *keys)
496
496
497 def _doDel(self, keys):
497 def _doDel(self, keys):
498 for key in keys:
498 for key in keys:
499 del self.properties[key]
499 del self.properties[key]
500
500
501 def del_properties(self, keys):
501 def del_properties(self, keys):
502 msg = {'engineid':self.id,
502 msg = {'engineid':self.id,
503 'method':'del_properties',
503 'method':'del_properties',
504 'args':[repr(keys)]}
504 'args':[repr(keys)]}
505 return self.executeAndRaise(msg, self._doDel, keys)
505 return self.executeAndRaise(msg, self._doDel, keys)
506
506
507 def _doHas(self, keys):
507 def _doHas(self, keys):
508 return [self.properties.has_key(key) for key in keys]
508 return [self.properties.has_key(key) for key in keys]
509
509
510 def has_properties(self, keys):
510 def has_properties(self, keys):
511 msg = {'engineid':self.id,
511 msg = {'engineid':self.id,
512 'method':'has_properties',
512 'method':'has_properties',
513 'args':[repr(keys)]}
513 'args':[repr(keys)]}
514 return self.executeAndRaise(msg, self._doHas, keys)
514 return self.executeAndRaise(msg, self._doHas, keys)
515
515
516 def clear_properties(self):
516 def clear_properties(self):
517 msg = {'engineid':self.id,
517 msg = {'engineid':self.id,
518 'method':'clear_properties',
518 'method':'clear_properties',
519 'args':[]}
519 'args':[]}
520 return self.executeAndRaise(msg, self.properties.clear)
520 return self.executeAndRaise(msg, self.properties.clear)
521
521
522 def push_serialized(self, sNamespace):
522 def push_serialized(self, sNamespace):
523 msg = {'engineid':self.id,
523 msg = {'engineid':self.id,
524 'method':'push_serialized',
524 'method':'push_serialized',
525 'args':[repr(sNamespace.keys())]}
525 'args':[repr(sNamespace.keys())]}
526 ns = {}
526 ns = {}
527 for k,v in sNamespace.iteritems():
527 for k,v in sNamespace.iteritems():
528 try:
528 try:
529 unserialized = newserialized.IUnSerialized(v)
529 unserialized = newserialized.IUnSerialized(v)
530 ns[k] = unserialized.getObject()
530 ns[k] = unserialized.getObject()
531 except:
531 except:
532 return defer.fail()
532 return defer.fail()
533 return self.executeAndRaise(msg, self.shell.push, ns)
533 return self.executeAndRaise(msg, self.shell.push, ns)
534
534
535 def pull_serialized(self, keys):
535 def pull_serialized(self, keys):
536 msg = {'engineid':self.id,
536 msg = {'engineid':self.id,
537 'method':'pull_serialized',
537 'method':'pull_serialized',
538 'args':[repr(keys)]}
538 'args':[repr(keys)]}
539 if isinstance(keys, str):
539 if isinstance(keys, str):
540 keys = [keys]
540 keys = [keys]
541 if len(keys)==1:
541 if len(keys)==1:
542 d = self.executeAndRaise(msg, self.shell.pull, keys)
542 d = self.executeAndRaise(msg, self.shell.pull, keys)
543 d.addCallback(newserialized.serialize)
543 d.addCallback(newserialized.serialize)
544 return d
544 return d
545 elif len(keys)>1:
545 elif len(keys)>1:
546 d = self.executeAndRaise(msg, self.shell.pull, keys)
546 d = self.executeAndRaise(msg, self.shell.pull, keys)
547 @d.addCallback
547 @d.addCallback
548 def packThemUp(values):
548 def packThemUp(values):
549 serials = []
549 serials = []
550 for v in values:
550 for v in values:
551 try:
551 try:
552 serials.append(newserialized.serialize(v))
552 serials.append(newserialized.serialize(v))
553 except:
553 except:
554 return defer.fail(failure.Failure())
554 return defer.fail(failure.Failure())
555 return serials
555 return serials
556 return packThemUp
556 return packThemUp
557
557
558
558
559 def queue(methodToQueue):
559 def queue(methodToQueue):
560 def queuedMethod(this, *args, **kwargs):
560 def queuedMethod(this, *args, **kwargs):
561 name = methodToQueue.__name__
561 name = methodToQueue.__name__
562 return this.submitCommand(Command(name, *args, **kwargs))
562 return this.submitCommand(Command(name, *args, **kwargs))
563 return queuedMethod
563 return queuedMethod
564
564
565 class QueuedEngine(object):
565 class QueuedEngine(object):
566 """Adapt an IEngineBase to an IEngineQueued by wrapping it.
566 """Adapt an IEngineBase to an IEngineQueued by wrapping it.
567
567
568 The resulting object will implement IEngineQueued which extends
568 The resulting object will implement IEngineQueued which extends
569 IEngineCore which extends (IEngineBase, IEngineSerialized).
569 IEngineCore which extends (IEngineBase, IEngineSerialized).
570
570
571 This seems like the best way of handling it, but I am not sure. The
571 This seems like the best way of handling it, but I am not sure. The
572 other option is to have the various base interfaces be used like
572 other option is to have the various base interfaces be used like
573 mix-in intefaces. The problem I have with this is adpatation is
573 mix-in intefaces. The problem I have with this is adpatation is
574 more difficult and complicated because there can be can multiple
574 more difficult and complicated because there can be can multiple
575 original and final Interfaces.
575 original and final Interfaces.
576 """
576 """
577
577
578 zi.implements(IEngineQueued)
578 zi.implements(IEngineQueued)
579
579
580 def __init__(self, engine):
580 def __init__(self, engine):
581 """Create a QueuedEngine object from an engine
581 """Create a QueuedEngine object from an engine
582
582
583 engine: An implementor of IEngineCore and IEngineSerialized
583 engine: An implementor of IEngineCore and IEngineSerialized
584 keepUpToDate: whether to update the remote status when the
584 keepUpToDate: whether to update the remote status when the
585 queue is empty. Defaults to False.
585 queue is empty. Defaults to False.
586 """
586 """
587
587
588 # This is the right way to do these tests rather than
588 # This is the right way to do these tests rather than
589 # IEngineCore in list(zi.providedBy(engine)) which will only
589 # IEngineCore in list(zi.providedBy(engine)) which will only
590 # picks of the interfaces that are directly declared by engine.
590 # picks of the interfaces that are directly declared by engine.
591 assert IEngineBase.providedBy(engine), \
591 assert IEngineBase.providedBy(engine), \
592 "engine passed to QueuedEngine doesn't provide IEngineBase"
592 "engine passed to QueuedEngine doesn't provide IEngineBase"
593
593
594 self.engine = engine
594 self.engine = engine
595 self.id = engine.id
595 self.id = engine.id
596 self.queued = []
596 self.queued = []
597 self.history = {}
597 self.history = {}
598 self.engineStatus = {}
598 self.engineStatus = {}
599 self.currentCommand = None
599 self.currentCommand = None
600 self.failureObservers = []
600 self.failureObservers = []
601
601
602 def _get_properties(self):
602 def _get_properties(self):
603 return self.engine.properties
603 return self.engine.properties
604
604
605 properties = property(_get_properties, lambda self, _: None)
605 properties = property(_get_properties, lambda self, _: None)
606 # Queue management methods. You should not call these directly
606 # Queue management methods. You should not call these directly
607
607
608 def submitCommand(self, cmd):
608 def submitCommand(self, cmd):
609 """Submit command to queue."""
609 """Submit command to queue."""
610
610
611 d = defer.Deferred()
611 d = defer.Deferred()
612 cmd.setDeferred(d)
612 cmd.setDeferred(d)
613 if self.currentCommand is not None:
613 if self.currentCommand is not None:
614 if self.currentCommand.finished:
614 if self.currentCommand.finished:
615 # log.msg("Running command immediately: %r" % cmd)
615 # log.msg("Running command immediately: %r" % cmd)
616 self.currentCommand = cmd
616 self.currentCommand = cmd
617 self.runCurrentCommand()
617 self.runCurrentCommand()
618 else: # command is still running
618 else: # command is still running
619 # log.msg("Command is running: %r" % self.currentCommand)
619 # log.msg("Command is running: %r" % self.currentCommand)
620 # log.msg("Queueing: %r" % cmd)
620 # log.msg("Queueing: %r" % cmd)
621 self.queued.append(cmd)
621 self.queued.append(cmd)
622 else:
622 else:
623 # log.msg("No current commands, running: %r" % cmd)
623 # log.msg("No current commands, running: %r" % cmd)
624 self.currentCommand = cmd
624 self.currentCommand = cmd
625 self.runCurrentCommand()
625 self.runCurrentCommand()
626 return d
626 return d
627
627
628 def runCurrentCommand(self):
628 def runCurrentCommand(self):
629 """Run current command."""
629 """Run current command."""
630
630
631 cmd = self.currentCommand
631 cmd = self.currentCommand
632 f = getattr(self.engine, cmd.remoteMethod, None)
632 f = getattr(self.engine, cmd.remoteMethod, None)
633 if f:
633 if f:
634 d = f(*cmd.args, **cmd.kwargs)
634 d = f(*cmd.args, **cmd.kwargs)
635 if cmd.remoteMethod is 'execute':
635 if cmd.remoteMethod is 'execute':
636 d.addCallback(self.saveResult)
636 d.addCallback(self.saveResult)
637 d.addCallback(self.finishCommand)
637 d.addCallback(self.finishCommand)
638 d.addErrback(self.abortCommand)
638 d.addErrback(self.abortCommand)
639 else:
639 else:
640 return defer.fail(AttributeError(cmd.remoteMethod))
640 return defer.fail(AttributeError(cmd.remoteMethod))
641
641
642 def _flushQueue(self):
642 def _flushQueue(self):
643 """Pop next command in queue and run it."""
643 """Pop next command in queue and run it."""
644
644
645 if len(self.queued) > 0:
645 if len(self.queued) > 0:
646 self.currentCommand = self.queued.pop(0)
646 self.currentCommand = self.queued.pop(0)
647 self.runCurrentCommand()
647 self.runCurrentCommand()
648
648
649 def saveResult(self, result):
649 def saveResult(self, result):
650 """Put the result in the history."""
650 """Put the result in the history."""
651 self.history[result['number']] = result
651 self.history[result['number']] = result
652 return result
652 return result
653
653
654 def finishCommand(self, result):
654 def finishCommand(self, result):
655 """Finish currrent command."""
655 """Finish currrent command."""
656
656
657 # The order of these commands is absolutely critical.
657 # The order of these commands is absolutely critical.
658 self.currentCommand.handleResult(result)
658 self.currentCommand.handleResult(result)
659 self.currentCommand.finished = True
659 self.currentCommand.finished = True
660 self._flushQueue()
660 self._flushQueue()
661 return result
661 return result
662
662
663 def abortCommand(self, reason):
663 def abortCommand(self, reason):
664 """Abort current command.
664 """Abort current command.
665
665
666 This eats the Failure but first passes it onto the Deferred that the
666 This eats the Failure but first passes it onto the Deferred that the
667 user has.
667 user has.
668
668
669 It also clear out the queue so subsequence commands don't run.
669 It also clear out the queue so subsequence commands don't run.
670 """
670 """
671
671
672 # The order of these 3 commands is absolutely critical. The currentCommand
672 # The order of these 3 commands is absolutely critical. The currentCommand
673 # must first be marked as finished BEFORE the queue is cleared and before
673 # must first be marked as finished BEFORE the queue is cleared and before
674 # the current command is sent the failure.
674 # the current command is sent the failure.
675 # Also, the queue must be cleared BEFORE the current command is sent the Failure
675 # Also, the queue must be cleared BEFORE the current command is sent the Failure
676 # otherwise the errback chain could trigger new commands to be added to the
676 # otherwise the errback chain could trigger new commands to be added to the
677 # queue before we clear it. We should clear ONLY the commands that were in
677 # queue before we clear it. We should clear ONLY the commands that were in
678 # the queue when the error occured.
678 # the queue when the error occured.
679 self.currentCommand.finished = True
679 self.currentCommand.finished = True
680 s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs)
680 s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs)
681 self.clear_queue(msg=s)
681 self.clear_queue(msg=s)
682 self.currentCommand.handleError(reason)
682 self.currentCommand.handleError(reason)
683
683
684 return None
684 return None
685
685
686 #---------------------------------------------------------------------------
686 #---------------------------------------------------------------------------
687 # IEngineCore methods
687 # IEngineCore methods
688 #---------------------------------------------------------------------------
688 #---------------------------------------------------------------------------
689
689
690 @queue
690 @queue
691 def execute(self, lines):
691 def execute(self, lines):
692 pass
692 pass
693
693
694 @queue
694 @queue
695 def push(self, namespace):
695 def push(self, namespace):
696 pass
696 pass
697
697
698 @queue
698 @queue
699 def pull(self, keys):
699 def pull(self, keys):
700 pass
700 pass
701
701
702 @queue
702 @queue
703 def push_function(self, namespace):
703 def push_function(self, namespace):
704 pass
704 pass
705
705
706 @queue
706 @queue
707 def pull_function(self, keys):
707 def pull_function(self, keys):
708 pass
708 pass
709
709
710 def get_result(self, i=None):
710 def get_result(self, i=None):
711 if i is None:
711 if i is None:
712 i = max(self.history.keys()+[None])
712 i = max(self.history.keys()+[None])
713
713
714 cmd = self.history.get(i, None)
714 cmd = self.history.get(i, None)
715 # Uncomment this line to disable chaching of results
715 # Uncomment this line to disable chaching of results
716 #cmd = None
716 #cmd = None
717 if cmd is None:
717 if cmd is None:
718 return self.submitCommand(Command('get_result', i))
718 return self.submitCommand(Command('get_result', i))
719 else:
719 else:
720 return defer.succeed(cmd)
720 return defer.succeed(cmd)
721
721
722 def reset(self):
722 def reset(self):
723 self.clear_queue()
723 self.clear_queue()
724 self.history = {} # reset the cache - I am not sure we should do this
724 self.history = {} # reset the cache - I am not sure we should do this
725 return self.submitCommand(Command('reset'))
725 return self.submitCommand(Command('reset'))
726
726
727 def kill(self):
727 def kill(self):
728 self.clear_queue()
728 self.clear_queue()
729 return self.submitCommand(Command('kill'))
729 return self.submitCommand(Command('kill'))
730
730
731 @queue
731 @queue
732 def keys(self):
732 def keys(self):
733 pass
733 pass
734
734
735 #---------------------------------------------------------------------------
735 #---------------------------------------------------------------------------
736 # IEngineSerialized methods
736 # IEngineSerialized methods
737 #---------------------------------------------------------------------------
737 #---------------------------------------------------------------------------
738
738
739 @queue
739 @queue
740 def push_serialized(self, namespace):
740 def push_serialized(self, namespace):
741 pass
741 pass
742
742
743 @queue
743 @queue
744 def pull_serialized(self, keys):
744 def pull_serialized(self, keys):
745 pass
745 pass
746
746
747 #---------------------------------------------------------------------------
747 #---------------------------------------------------------------------------
748 # IEngineProperties methods
748 # IEngineProperties methods
749 #---------------------------------------------------------------------------
749 #---------------------------------------------------------------------------
750
750
751 @queue
751 @queue
752 def set_properties(self, namespace):
752 def set_properties(self, namespace):
753 pass
753 pass
754
754
755 @queue
755 @queue
756 def get_properties(self, keys=None):
756 def get_properties(self, keys=None):
757 pass
757 pass
758
758
759 @queue
759 @queue
760 def del_properties(self, keys):
760 def del_properties(self, keys):
761 pass
761 pass
762
762
763 @queue
763 @queue
764 def has_properties(self, keys):
764 def has_properties(self, keys):
765 pass
765 pass
766
766
767 @queue
767 @queue
768 def clear_properties(self):
768 def clear_properties(self):
769 pass
769 pass
770
770
771 #---------------------------------------------------------------------------
771 #---------------------------------------------------------------------------
772 # IQueuedEngine methods
772 # IQueuedEngine methods
773 #---------------------------------------------------------------------------
773 #---------------------------------------------------------------------------
774
774
775 def clear_queue(self, msg=''):
775 def clear_queue(self, msg=''):
776 """Clear the queue, but doesn't cancel the currently running commmand."""
776 """Clear the queue, but doesn't cancel the currently running commmand."""
777
777
778 for cmd in self.queued:
778 for cmd in self.queued:
779 cmd.deferred.errback(failure.Failure(error.QueueCleared(msg)))
779 cmd.deferred.errback(failure.Failure(error.QueueCleared(msg)))
780 self.queued = []
780 self.queued = []
781 return defer.succeed(None)
781 return defer.succeed(None)
782
782
783 def queue_status(self):
783 def queue_status(self):
784 if self.currentCommand is not None:
784 if self.currentCommand is not None:
785 if self.currentCommand.finished:
785 if self.currentCommand.finished:
786 pending = repr(None)
786 pending = repr(None)
787 else:
787 else:
788 pending = repr(self.currentCommand)
788 pending = repr(self.currentCommand)
789 else:
789 else:
790 pending = repr(None)
790 pending = repr(None)
791 dikt = {'queue':map(repr,self.queued), 'pending':pending}
791 dikt = {'queue':map(repr,self.queued), 'pending':pending}
792 return defer.succeed(dikt)
792 return defer.succeed(dikt)
793
793
794 def register_failure_observer(self, obs):
794 def register_failure_observer(self, obs):
795 self.failureObservers.append(obs)
795 self.failureObservers.append(obs)
796
796
797 def unregister_failure_observer(self, obs):
797 def unregister_failure_observer(self, obs):
798 self.failureObservers.remove(obs)
798 self.failureObservers.remove(obs)
799
799
800
800
801 # Now register QueuedEngine as an adpater class that makes an IEngineBase into a
801 # Now register QueuedEngine as an adpater class that makes an IEngineBase into a
802 # IEngineQueued.
802 # IEngineQueued.
803 components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued)
803 components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued)
804
804
805
805
806 class Command(object):
806 class Command(object):
807 """A command object that encapslates queued commands.
807 """A command object that encapslates queued commands.
808
808
809 This class basically keeps track of a command that has been queued
809 This class basically keeps track of a command that has been queued
810 in a QueuedEngine. It manages the deferreds and hold the method to be called
810 in a QueuedEngine. It manages the deferreds and hold the method to be called
811 and the arguments to that method.
811 and the arguments to that method.
812 """
812 """
813
813
814
814
815 def __init__(self, remoteMethod, *args, **kwargs):
815 def __init__(self, remoteMethod, *args, **kwargs):
816 """Build a new Command object."""
816 """Build a new Command object."""
817
817
818 self.remoteMethod = remoteMethod
818 self.remoteMethod = remoteMethod
819 self.args = args
819 self.args = args
820 self.kwargs = kwargs
820 self.kwargs = kwargs
821 self.finished = False
821 self.finished = False
822
822
823 def setDeferred(self, d):
823 def setDeferred(self, d):
824 """Sets the deferred attribute of the Command."""
824 """Sets the deferred attribute of the Command."""
825
825
826 self.deferred = d
826 self.deferred = d
827
827
828 def __repr__(self):
828 def __repr__(self):
829 if not self.args:
829 if not self.args:
830 args = ''
830 args = ''
831 else:
831 else:
832 args = str(self.args)[1:-2] #cut off (...,)
832 args = str(self.args)[1:-2] #cut off (...,)
833 for k,v in self.kwargs.iteritems():
833 for k,v in self.kwargs.iteritems():
834 if args:
834 if args:
835 args += ', '
835 args += ', '
836 args += '%s=%r' %(k,v)
836 args += '%s=%r' %(k,v)
837 return "%s(%s)" %(self.remoteMethod, args)
837 return "%s(%s)" %(self.remoteMethod, args)
838
838
839 def handleResult(self, result):
839 def handleResult(self, result):
840 """When the result is ready, relay it to self.deferred."""
840 """When the result is ready, relay it to self.deferred."""
841
841
842 self.deferred.callback(result)
842 self.deferred.callback(result)
843
843
844 def handleError(self, reason):
844 def handleError(self, reason):
845 """When an error has occured, relay it to self.deferred."""
845 """When an error has occured, relay it to self.deferred."""
846
846
847 self.deferred.errback(reason)
847 self.deferred.errback(reason)
848
848
849 class ThreadedEngineService(EngineService):
849 class ThreadedEngineService(EngineService):
850 """An EngineService subclass that defers execute commands to a separate thread.
850 """An EngineService subclass that defers execute commands to a separate
851 thread.
851
852
852 ThreadedEngineService uses twisted.internet.threads.deferToThread to defer execute
853 ThreadedEngineService uses twisted.internet.threads.deferToThread to
853 requests to a separate thread. GUI frontends may want to use ThreadedEngineService as
854 defer execute requests to a separate thread. GUI frontends may want to
854 the engine in an IPython.frontend.frontendbase.FrontEndBase subclass to prevent
855 use ThreadedEngineService as the engine in an
856 IPython.frontend.frontendbase.FrontEndBase subclass to prevent
855 block execution from blocking the GUI thread.
857 block execution from blocking the GUI thread.
856 """
858 """
857
859
858 zi.implements(IEngineBase)
860 zi.implements(IEngineBase)
859
861
860 def __init__(self, shellClass=Interpreter, mpi=None):
862 def __init__(self, shellClass=Interpreter, mpi=None):
861 EngineService.__init__(self, shellClass, mpi)
863 EngineService.__init__(self, shellClass, mpi)
862
864
863
865
864 def execute(self, lines):
866 def execute(self, lines):
865 # Only import this if we are going to use this class
867 # Only import this if we are going to use this class
866 from twisted.internet import threads
868 from twisted.internet import threads
867
869
868 msg = {'engineid':self.id,
870 msg = {'engineid':self.id,
869 'method':'execute',
871 'method':'execute',
870 'args':[lines]}
872 'args':[lines]}
871
873
872 d = threads.deferToThread(self.shell.execute, lines)
874 d = threads.deferToThread(self.shell.execute, lines)
873 d.addCallback(self.addIDToResult)
875 d.addCallback(self.addIDToResult)
874 return d
876 return d
1 NO CONTENT: file renamed from docs/ChangeLog to docs/attic/ChangeLog
NO CONTENT: file renamed from docs/ChangeLog to docs/attic/ChangeLog
@@ -1,161 +1,162 b''
1 .. _changes:
1 .. _changes:
2
2
3 ==========
3 ==========
4 What's new
4 What's new
5 ==========
5 ==========
6
6
7 .. contents::
7 .. contents::
8
8
9 Release 0.9
9 Release 0.9
10 ===========
10 ===========
11
11
12 New features
12 New features
13 ------------
13 ------------
14
14
15 * All of the parallel computing capabilities from `ipython1-dev` have been merged into
15 * All of the parallel computing capabilities from `ipython1-dev` have been merged into
16 IPython proper. This resulted in the following new subpackages:
16 IPython proper. This resulted in the following new subpackages:
17 :mod:`IPython.kernel`, :mod:`IPython.kernel.core`, :mod:`IPython.config`,
17 :mod:`IPython.kernel`, :mod:`IPython.kernel.core`, :mod:`IPython.config`,
18 :mod:`IPython.tools` and :mod:`IPython.testing`.
18 :mod:`IPython.tools` and :mod:`IPython.testing`.
19 * As part of merging in the `ipython1-dev` stuff, the `setup.py` script and friends
19 * As part of merging in the `ipython1-dev` stuff, the `setup.py` script and friends
20 have been completely refactored. Now we are checking for dependencies using
20 have been completely refactored. Now we are checking for dependencies using
21 the approach that matplotlib uses.
21 the approach that matplotlib uses.
22 * The documentation has been completely reorganized to accept the documentation
22 * The documentation has been completely reorganized to accept the documentation
23 from `ipython1-dev`.
23 from `ipython1-dev`.
24 * We have switched to using Foolscap for all of our network protocols in
24 * We have switched to using Foolscap for all of our network protocols in
25 :mod:`IPython.kernel`. This gives us secure connections that are both encrypted
25 :mod:`IPython.kernel`. This gives us secure connections that are both encrypted
26 and authenticated.
26 and authenticated.
27 * We have a brand new `COPYING.txt` files that describes the IPython license
27 * We have a brand new `COPYING.txt` files that describes the IPython license
28 and copyright. The biggest change is that we are putting "The IPython
28 and copyright. The biggest change is that we are putting "The IPython
29 Development Team" as the copyright holder. We give more details about exactly
29 Development Team" as the copyright holder. We give more details about exactly
30 what this means in this file. All developer should read this and use the new
30 what this means in this file. All developer should read this and use the new
31 banner in all IPython source code files.
31 banner in all IPython source code files.
32 * sh profile: ./foo runs foo as system command, no need to do !./foo anymore
32
33
33 Bug fixes
34 Bug fixes
34 ---------
35 ---------
35
36
36 * A few subpackages has missing `__init__.py` files.
37 * A few subpackages has missing `__init__.py` files.
37 * The documentation is only created is Sphinx is found. Previously, the `setup.py`
38 * The documentation is only created is Sphinx is found. Previously, the `setup.py`
38 script would fail if it was missing.
39 script would fail if it was missing.
39
40
40 Backwards incompatible changes
41 Backwards incompatible changes
41 ------------------------------
42 ------------------------------
42
43
43 * IPython has a larger set of dependencies if you want all of its capabilities.
44 * IPython has a larger set of dependencies if you want all of its capabilities.
44 See the `setup.py` script for details.
45 See the `setup.py` script for details.
45 * The constructors for :class:`IPython.kernel.client.MultiEngineClient` and
46 * The constructors for :class:`IPython.kernel.client.MultiEngineClient` and
46 :class:`IPython.kernel.client.TaskClient` no longer take the (ip,port) tuple.
47 :class:`IPython.kernel.client.TaskClient` no longer take the (ip,port) tuple.
47 Instead they take the filename of a file that contains the FURL for that
48 Instead they take the filename of a file that contains the FURL for that
48 client. If the FURL file is in your IPYTHONDIR, it will be found automatically
49 client. If the FURL file is in your IPYTHONDIR, it will be found automatically
49 and the constructor can be left empty.
50 and the constructor can be left empty.
50 * The asynchronous clients in :mod:`IPython.kernel.asyncclient` are now created
51 * The asynchronous clients in :mod:`IPython.kernel.asyncclient` are now created
51 using the factory functions :func:`get_multiengine_client` and
52 using the factory functions :func:`get_multiengine_client` and
52 :func:`get_task_client`. These return a `Deferred` to the actual client.
53 :func:`get_task_client`. These return a `Deferred` to the actual client.
53 * The command line options to `ipcontroller` and `ipengine` have changed to
54 * The command line options to `ipcontroller` and `ipengine` have changed to
54 reflect the new Foolscap network protocol and the FURL files. Please see the
55 reflect the new Foolscap network protocol and the FURL files. Please see the
55 help for these scripts for details.
56 help for these scripts for details.
56 * The configuration files for the kernel have changed because of the Foolscap stuff.
57 * The configuration files for the kernel have changed because of the Foolscap stuff.
57 If you were using custom config files before, you should delete them and regenerate
58 If you were using custom config files before, you should delete them and regenerate
58 new ones.
59 new ones.
59
60
60 Changes merged in from IPython1
61 Changes merged in from IPython1
61 -------------------------------
62 -------------------------------
62
63
63 New features
64 New features
64 ............
65 ............
65
66
66 * Much improved ``setup.py`` and ``setupegg.py`` scripts. Because Twisted
67 * Much improved ``setup.py`` and ``setupegg.py`` scripts. Because Twisted
67 and zope.interface are now easy installable, we can declare them as dependencies
68 and zope.interface are now easy installable, we can declare them as dependencies
68 in our setupegg.py script.
69 in our setupegg.py script.
69 * IPython is now compatible with Twisted 2.5.0 and 8.x.
70 * IPython is now compatible with Twisted 2.5.0 and 8.x.
70 * Added a new example of how to use :mod:`ipython1.kernel.asynclient`.
71 * Added a new example of how to use :mod:`ipython1.kernel.asynclient`.
71 * Initial draft of a process daemon in :mod:`ipython1.daemon`. This has not
72 * Initial draft of a process daemon in :mod:`ipython1.daemon`. This has not
72 been merged into IPython and is still in `ipython1-dev`.
73 been merged into IPython and is still in `ipython1-dev`.
73 * The ``TaskController`` now has methods for getting the queue status.
74 * The ``TaskController`` now has methods for getting the queue status.
74 * The ``TaskResult`` objects not have information about how long the task
75 * The ``TaskResult`` objects not have information about how long the task
75 took to run.
76 took to run.
76 * We are attaching additional attributes to exceptions ``(_ipython_*)`` that
77 * We are attaching additional attributes to exceptions ``(_ipython_*)`` that
77 we use to carry additional info around.
78 we use to carry additional info around.
78 * New top-level module :mod:`asyncclient` that has asynchronous versions (that
79 * New top-level module :mod:`asyncclient` that has asynchronous versions (that
79 return deferreds) of the client classes. This is designed to users who want
80 return deferreds) of the client classes. This is designed to users who want
80 to run their own Twisted reactor
81 to run their own Twisted reactor
81 * All the clients in :mod:`client` are now based on Twisted. This is done by
82 * All the clients in :mod:`client` are now based on Twisted. This is done by
82 running the Twisted reactor in a separate thread and using the
83 running the Twisted reactor in a separate thread and using the
83 :func:`blockingCallFromThread` function that is in recent versions of Twisted.
84 :func:`blockingCallFromThread` function that is in recent versions of Twisted.
84 * Functions can now be pushed/pulled to/from engines using
85 * Functions can now be pushed/pulled to/from engines using
85 :meth:`MultiEngineClient.push_function` and :meth:`MultiEngineClient.pull_function`.
86 :meth:`MultiEngineClient.push_function` and :meth:`MultiEngineClient.pull_function`.
86 * Gather/scatter are now implemented in the client to reduce the work load
87 * Gather/scatter are now implemented in the client to reduce the work load
87 of the controller and improve performance.
88 of the controller and improve performance.
88 * Complete rewrite of the IPython docuementation. All of the documentation
89 * Complete rewrite of the IPython docuementation. All of the documentation
89 from the IPython website has been moved into docs/source as restructured
90 from the IPython website has been moved into docs/source as restructured
90 text documents. PDF and HTML documentation are being generated using
91 text documents. PDF and HTML documentation are being generated using
91 Sphinx.
92 Sphinx.
92 * New developer oriented documentation: development guidelines and roadmap.
93 * New developer oriented documentation: development guidelines and roadmap.
93 * Traditional ``ChangeLog`` has been changed to a more useful ``changes.txt`` file
94 * Traditional ``ChangeLog`` has been changed to a more useful ``changes.txt`` file
94 that is organized by release and is meant to provide something more relevant
95 that is organized by release and is meant to provide something more relevant
95 for users.
96 for users.
96
97
97 Bug fixes
98 Bug fixes
98 .........
99 .........
99
100
100 * Created a proper ``MANIFEST.in`` file to create source distributions.
101 * Created a proper ``MANIFEST.in`` file to create source distributions.
101 * Fixed a bug in the ``MultiEngine`` interface. Previously, multi-engine
102 * Fixed a bug in the ``MultiEngine`` interface. Previously, multi-engine
102 actions were being collected with a :class:`DeferredList` with
103 actions were being collected with a :class:`DeferredList` with
103 ``fireononeerrback=1``. This meant that methods were returning
104 ``fireononeerrback=1``. This meant that methods were returning
104 before all engines had given their results. This was causing extremely odd
105 before all engines had given their results. This was causing extremely odd
105 bugs in certain cases. To fix this problem, we have 1) set
106 bugs in certain cases. To fix this problem, we have 1) set
106 ``fireononeerrback=0`` to make sure all results (or exceptions) are in
107 ``fireononeerrback=0`` to make sure all results (or exceptions) are in
107 before returning and 2) introduced a :exc:`CompositeError` exception
108 before returning and 2) introduced a :exc:`CompositeError` exception
108 that wraps all of the engine exceptions. This is a huge change as it means
109 that wraps all of the engine exceptions. This is a huge change as it means
109 that users will have to catch :exc:`CompositeError` rather than the actual
110 that users will have to catch :exc:`CompositeError` rather than the actual
110 exception.
111 exception.
111
112
112 Backwards incompatible changes
113 Backwards incompatible changes
113 ..............................
114 ..............................
114
115
115 * All names have been renamed to conform to the lowercase_with_underscore
116 * All names have been renamed to conform to the lowercase_with_underscore
116 convention. This will require users to change references to all names like
117 convention. This will require users to change references to all names like
117 ``queueStatus`` to ``queue_status``.
118 ``queueStatus`` to ``queue_status``.
118 * Previously, methods like :meth:`MultiEngineClient.push` and
119 * Previously, methods like :meth:`MultiEngineClient.push` and
119 :meth:`MultiEngineClient.push` used ``*args`` and ``**kwargs``. This was
120 :meth:`MultiEngineClient.push` used ``*args`` and ``**kwargs``. This was
120 becoming a problem as we weren't able to introduce new keyword arguments into
121 becoming a problem as we weren't able to introduce new keyword arguments into
121 the API. Now these methods simple take a dict or sequence. This has also allowed
122 the API. Now these methods simple take a dict or sequence. This has also allowed
122 us to get rid of the ``*All`` methods like :meth:`pushAll` and :meth:`pullAll`.
123 us to get rid of the ``*All`` methods like :meth:`pushAll` and :meth:`pullAll`.
123 These things are now handled with the ``targets`` keyword argument that defaults
124 These things are now handled with the ``targets`` keyword argument that defaults
124 to ``'all'``.
125 to ``'all'``.
125 * The :attr:`MultiEngineClient.magicTargets` has been renamed to
126 * The :attr:`MultiEngineClient.magicTargets` has been renamed to
126 :attr:`MultiEngineClient.targets`.
127 :attr:`MultiEngineClient.targets`.
127 * All methods in the MultiEngine interface now accept the optional keyword argument
128 * All methods in the MultiEngine interface now accept the optional keyword argument
128 ``block``.
129 ``block``.
129 * Renamed :class:`RemoteController` to :class:`MultiEngineClient` and
130 * Renamed :class:`RemoteController` to :class:`MultiEngineClient` and
130 :class:`TaskController` to :class:`TaskClient`.
131 :class:`TaskController` to :class:`TaskClient`.
131 * Renamed the top-level module from :mod:`api` to :mod:`client`.
132 * Renamed the top-level module from :mod:`api` to :mod:`client`.
132 * Most methods in the multiengine interface now raise a :exc:`CompositeError` exception
133 * Most methods in the multiengine interface now raise a :exc:`CompositeError` exception
133 that wraps the user's exceptions, rather than just raising the raw user's exception.
134 that wraps the user's exceptions, rather than just raising the raw user's exception.
134 * Changed the ``setupNS`` and ``resultNames`` in the ``Task`` class to ``push``
135 * Changed the ``setupNS`` and ``resultNames`` in the ``Task`` class to ``push``
135 and ``pull``.
136 and ``pull``.
136
137
137 Release 0.8.4
138 Release 0.8.4
138 =============
139 =============
139
140
140 Someone needs to describe what went into 0.8.4.
141 Someone needs to describe what went into 0.8.4.
141
142
142 Release 0.8.2
143 Release 0.8.2
143 =============
144 =============
144
145
145 * %pushd/%popd behave differently; now "pushd /foo" pushes CURRENT directory
146 * %pushd/%popd behave differently; now "pushd /foo" pushes CURRENT directory
146 and jumps to /foo. The current behaviour is closer to the documented
147 and jumps to /foo. The current behaviour is closer to the documented
147 behaviour, and should not trip anyone.
148 behaviour, and should not trip anyone.
148
149
149 Release 0.8.3
150 Release 0.8.3
150 =============
151 =============
151
152
152 * pydb is now disabled by default (due to %run -d problems). You can enable
153 * pydb is now disabled by default (due to %run -d problems). You can enable
153 it by passing -pydb command line argument to IPython. Note that setting
154 it by passing -pydb command line argument to IPython. Note that setting
154 it in config file won't work.
155 it in config file won't work.
155
156
156 Older releases
157 Older releases
157 ==============
158 ==============
158
159
159 Changes in earlier releases of IPython are described in the older file ``ChangeLog``.
160 Changes in earlier releases of IPython are described in the older file ``ChangeLog``.
160 Please refer to this document for details.
161 Please refer to this document for details.
161
162
@@ -1,315 +1,360 b''
1 .. _development:
1 .. _development:
2
2
3 ==================================
3 ==================================
4 IPython development guidelines
4 IPython development guidelines
5 ==================================
5 ==================================
6
6
7 .. contents::
7 .. contents::
8 ..
9 1 Overview
10 2 Project organization
11 2.1 Subpackages
12 2.2 Installation and dependencies
13 2.3 Specific subpackages
14 3 Version control
15 4 Documentation
16 4.1 Standalone documentation
17 4.2 Docstring format
18 5 Coding conventions
19 5.1 General
20 5.2 Naming conventions
21 6 Testing
22 7 Configuration
23 ..
24
8
25
9
26 Overview
10 Overview
27 ========
11 ========
28
12
29 IPython is the next generation of IPython. It is named such for two reasons:
13 IPython is the next generation of IPython. It is named such for two reasons:
30
14
31 - Eventually, IPython will become IPython version 1.0.
15 - Eventually, IPython will become IPython version 1.0.
32 - This new code base needs to be able to co-exist with the existing IPython until
16 - This new code base needs to be able to co-exist with the existing IPython until
33 it is a full replacement for it. Thus we needed a different name. We couldn't
17 it is a full replacement for it. Thus we needed a different name. We couldn't
34 use ``ipython`` (lowercase) as some files systems are case insensitive.
18 use ``ipython`` (lowercase) as some files systems are case insensitive.
35
19
36 There are two, no three, main goals of the IPython effort:
20 There are two, no three, main goals of the IPython effort:
37
21
38 1. Clean up the existing codebase and write lots of tests.
22 1. Clean up the existing codebase and write lots of tests.
39 2. Separate the core functionality of IPython from the terminal to enable IPython
23 2. Separate the core functionality of IPython from the terminal to enable IPython
40 to be used from within a variety of GUI applications.
24 to be used from within a variety of GUI applications.
41 3. Implement a system for interactive parallel computing.
25 3. Implement a system for interactive parallel computing.
42
26
43 While the third goal may seem a bit unrelated to the main focus of IPython, it turns
27 While the third goal may seem a bit unrelated to the main focus of IPython, it turns
44 out that the technologies required for this goal are nearly identical with those
28 out that the technologies required for this goal are nearly identical with those
45 required for goal two. This is the main reason the interactive parallel computing
29 required for goal two. This is the main reason the interactive parallel computing
46 capabilities are being put into IPython proper. Currently the third of these goals is
30 capabilities are being put into IPython proper. Currently the third of these goals is
47 furthest along.
31 furthest along.
48
32
49 This document describes IPython from the perspective of developers.
33 This document describes IPython from the perspective of developers.
50
34
51
35
52 Project organization
36 Project organization
53 ====================
37 ====================
54
38
55 Subpackages
39 Subpackages
56 -----------
40 -----------
57
41
58 IPython is organized into semi self-contained subpackages. Each of the subpackages will have its own:
42 IPython is organized into semi self-contained subpackages. Each of the subpackages will have its own:
59
43
60 - **Dependencies**. One of the most important things to keep in mind in
44 - **Dependencies**. One of the most important things to keep in mind in
61 partitioning code amongst subpackages, is that they should be used to cleanly
45 partitioning code amongst subpackages, is that they should be used to cleanly
62 encapsulate dependencies.
46 encapsulate dependencies.
63 - **Tests**. Each subpackage shoud have its own ``tests`` subdirectory that
47 - **Tests**. Each subpackage shoud have its own ``tests`` subdirectory that
64 contains all of the tests for that package. For information about writing tests
48 contains all of the tests for that package. For information about writing tests
65 for IPython, see the `Testing System`_ section of this document.
49 for IPython, see the `Testing System`_ section of this document.
66 - **Configuration**. Each subpackage should have its own ``config`` subdirectory
50 - **Configuration**. Each subpackage should have its own ``config`` subdirectory
67 that contains the configuration information for the components of the
51 that contains the configuration information for the components of the
68 subpackage. For information about how the IPython configuration system
52 subpackage. For information about how the IPython configuration system
69 works, see the `Configuration System`_ section of this document.
53 works, see the `Configuration System`_ section of this document.
70 - **Scripts**. Each subpackage should have its own ``scripts`` subdirectory that
54 - **Scripts**. Each subpackage should have its own ``scripts`` subdirectory that
71 contains all of the command line scripts associated with the subpackage.
55 contains all of the command line scripts associated with the subpackage.
72
56
73 Installation and dependencies
57 Installation and dependencies
74 -----------------------------
58 -----------------------------
75
59
76 IPython will not use `setuptools`_ for installation. Instead, we will use standard
60 IPython will not use `setuptools`_ for installation. Instead, we will use standard
77 ``setup.py`` scripts that use `distutils`_. While there are a number a extremely nice
61 ``setup.py`` scripts that use `distutils`_. While there are a number a extremely nice
78 features that `setuptools`_ has (like namespace packages), the current implementation
62 features that `setuptools`_ has (like namespace packages), the current implementation
79 of `setuptools`_ has performance problems, particularly on shared file systems. In
63 of `setuptools`_ has performance problems, particularly on shared file systems. In
80 particular, when Python packages are installed on NSF file systems, import times
64 particular, when Python packages are installed on NSF file systems, import times
81 become much too long (up towards 10 seconds).
65 become much too long (up towards 10 seconds).
82
66
83 Because IPython is being used extensively in the context of high performance
67 Because IPython is being used extensively in the context of high performance
84 computing, where performance is critical but shared file systems are common, we feel
68 computing, where performance is critical but shared file systems are common, we feel
85 these performance hits are not acceptable. Thus, until the performance problems
69 these performance hits are not acceptable. Thus, until the performance problems
86 associated with `setuptools`_ are addressed, we will stick with plain `distutils`_. We
70 associated with `setuptools`_ are addressed, we will stick with plain `distutils`_. We
87 are hopeful that these problems will be addressed and that we will eventually begin
71 are hopeful that these problems will be addressed and that we will eventually begin
88 using `setuptools`_. Because of this, we are trying to organize IPython in a way that
72 using `setuptools`_. Because of this, we are trying to organize IPython in a way that
89 will make the eventual transition to `setuptools`_ as painless as possible.
73 will make the eventual transition to `setuptools`_ as painless as possible.
90
74
91 Because we will be using `distutils`_, there will be no method for automatically installing dependencies. Instead, we are following the approach of `Matplotlib`_ which can be summarized as follows:
75 Because we will be using `distutils`_, there will be no method for automatically installing dependencies. Instead, we are following the approach of `Matplotlib`_ which can be summarized as follows:
92
76
93 - Distinguish between required and optional dependencies. However, the required
77 - Distinguish between required and optional dependencies. However, the required
94 dependencies for IPython should be only the Python standard library.
78 dependencies for IPython should be only the Python standard library.
95 - Upon installation check to see which optional dependencies are present and tell
79 - Upon installation check to see which optional dependencies are present and tell
96 the user which parts of IPython need which optional dependencies.
80 the user which parts of IPython need which optional dependencies.
97
81
98 It is absolutely critical that each subpackage of IPython has a clearly specified set
82 It is absolutely critical that each subpackage of IPython has a clearly specified set
99 of dependencies and that dependencies are not carelessly inherited from other IPython
83 of dependencies and that dependencies are not carelessly inherited from other IPython
100 subpackages. Furthermore, tests that have certain dependencies should not fail if
84 subpackages. Furthermore, tests that have certain dependencies should not fail if
101 those dependencies are not present. Instead they should be skipped and print a
85 those dependencies are not present. Instead they should be skipped and print a
102 message.
86 message.
103
87
104 .. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools
88 .. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools
105 .. _distutils: http://docs.python.org/lib/module-distutils.html
89 .. _distutils: http://docs.python.org/lib/module-distutils.html
106 .. _Matplotlib: http://matplotlib.sourceforge.net/
90 .. _Matplotlib: http://matplotlib.sourceforge.net/
107
91
108 Specific subpackages
92 Specific subpackages
109 --------------------
93 --------------------
110
94
111 ``core``
95 ``core``
112 This is the core functionality of IPython that is independent of the
96 This is the core functionality of IPython that is independent of the
113 terminal, network and GUIs. Most of the code that is in the current
97 terminal, network and GUIs. Most of the code that is in the current
114 IPython trunk will be refactored, cleaned up and moved here.
98 IPython trunk will be refactored, cleaned up and moved here.
115
99
116 ``kernel``
100 ``kernel``
117 The enables the IPython core to be expose to a the network. This is
101 The enables the IPython core to be expose to a the network. This is
118 also where all of the parallel computing capabilities are to be found.
102 also where all of the parallel computing capabilities are to be found.
119
103
120 ``config``
104 ``config``
121 The configuration package used by IPython.
105 The configuration package used by IPython.
122
106
123 ``frontends``
107 ``frontends``
124 The various frontends for IPython. A frontend is the end-user application
108 The various frontends for IPython. A frontend is the end-user application
125 that exposes the capabilities of IPython to the user. The most basic frontend
109 that exposes the capabilities of IPython to the user. The most basic frontend
126 will simply be a terminal based application that looks just like today 's
110 will simply be a terminal based application that looks just like today 's
127 IPython. Other frontends will likely be more powerful and based on GUI toolkits.
111 IPython. Other frontends will likely be more powerful and based on GUI toolkits.
128
112
129 ``notebook``
113 ``notebook``
130 An application that allows users to work with IPython notebooks.
114 An application that allows users to work with IPython notebooks.
131
115
132 ``tools``
116 ``tools``
133 This is where general utilities go.
117 This is where general utilities go.
134
118
135
119
136 Version control
120 Version control
137 ===============
121 ===============
138
122
139 In the past, IPython development has been done using `Subversion`__. We are currently trying out `Bazaar`__ and `Launchpad`__.
123 In the past, IPython development has been done using `Subversion`__. Recently, we made the transition to using `Bazaar`__ and `Launchpad`__. This makes it much easier for people
124 to contribute code to IPython. Here is a sketch of how to use Bazaar for IPython
125 development. First, you should install Bazaar. After you have done that, make
126 sure that it is working by getting the latest main branch of IPython::
127
128 $ bzr branch lp:ipython
129
130 Now you can create a new branch for you to do your work in::
131
132 $ bzr branch ipython ipython-mybranch
133
134 The typical work cycle in this branch will be to make changes in `ipython-mybranch`
135 and then commit those changes using the commit command::
136
137 $ ...do work in ipython-mybranch...
138 $ bzr ci -m "the commit message goes here"
139
140 Please note that since we now don't use an old-style linear ChangeLog
141 (that tends to cause problems with distributed version control
142 systems), you should ensure that your log messages are reasonably
143 detailed. Use a docstring-like approach in the commit messages
144 (including the second line being left *blank*)::
145
146 Single line summary of changes being committed.
147
148 - more details when warranted ...
149 - including crediting outside contributors if they sent the
150 code/bug/idea!
151
152 If we couple this with a policy of making single commits for each
153 reasonably atomic change, the bzr log should give an excellent view of
154 the project, and the `--short` log option becomes a nice summary.
155
156 While working with this branch, it is a good idea to merge in changes that have been
157 made upstream in the parent branch. This can be done by doing::
158
159 $ bzr pull
160
161 If this command shows that the branches have diverged, then you should do a merge
162 instead::
163
164 $ bzr merge lp:ipython
165
166 If you want others to be able to see your branch, you can create an account with
167 launchpad and push the branch to your own workspace::
168
169 $ bzr push bzr+ssh://<me>@bazaar.launchpad.net/~<me>/+junk/ipython-mybranch
170
171 Finally, once the work in your branch is done, you can merge your changes back into
172 the `ipython` branch by using merge::
173
174 $ cd ipython
175 $ merge ../ipython-mybranch
176 [resolve any conflicts]
177 $ bzr ci -m "Fixing that bug"
178 $ bzr push
179
180 But this will require you to have write permissions to the `ipython` branch. It you don't
181 you can tell one of the IPython devs about your branch and they can do the merge for you.
182
183 More information about Bazaar workflows can be found `here`__.
140
184
141 .. __: http://subversion.tigris.org/
185 .. __: http://subversion.tigris.org/
142 .. __: http://bazaar-vcs.org/
186 .. __: http://bazaar-vcs.org/
143 .. __: http://www.launchpad.net/ipython
187 .. __: http://www.launchpad.net/ipython
188 .. __: http://doc.bazaar-vcs.org/bzr.dev/en/user-guide/index.html
144
189
145 Documentation
190 Documentation
146 =============
191 =============
147
192
148 Standalone documentation
193 Standalone documentation
149 ------------------------
194 ------------------------
150
195
151 All standalone documentation should be written in plain text (``.txt``) files using
196 All standalone documentation should be written in plain text (``.txt``) files using
152 `reStructuredText`_ for markup and formatting. All such documentation should be placed
197 `reStructuredText`_ for markup and formatting. All such documentation should be placed
153 in the top level directory ``docs`` of the IPython source tree. Or, when appropriate,
198 in the top level directory ``docs`` of the IPython source tree. Or, when appropriate,
154 a suitably named subdirectory should be used. The documentation in this location will
199 a suitably named subdirectory should be used. The documentation in this location will
155 serve as the main source for IPython documentation and all existing documentation
200 serve as the main source for IPython documentation and all existing documentation
156 should be converted to this format.
201 should be converted to this format.
157
202
158 In the future, the text files in the ``docs`` directory will be used to generate all
203 In the future, the text files in the ``docs`` directory will be used to generate all
159 forms of documentation for IPython. This include documentation on the IPython website
204 forms of documentation for IPython. This include documentation on the IPython website
160 as well as *pdf* documentation.
205 as well as *pdf* documentation.
161
206
162 .. _reStructuredText: http://docutils.sourceforge.net/rst.html
207 .. _reStructuredText: http://docutils.sourceforge.net/rst.html
163
208
164 Docstring format
209 Docstring format
165 ----------------
210 ----------------
166
211
167 Good docstrings are very important. All new code will use `Epydoc`_ for generating API
212 Good docstrings are very important. All new code will use `Epydoc`_ for generating API
168 docs, so we will follow the `Epydoc`_ conventions. More specifically, we will use
213 docs, so we will follow the `Epydoc`_ conventions. More specifically, we will use
169 `reStructuredText`_ for markup and formatting, since it is understood by a wide
214 `reStructuredText`_ for markup and formatting, since it is understood by a wide
170 variety of tools. This means that if in the future we have any reason to change from
215 variety of tools. This means that if in the future we have any reason to change from
171 `Epydoc`_ to something else, we'll have fewer transition pains.
216 `Epydoc`_ to something else, we'll have fewer transition pains.
172
217
173 Details about using `reStructuredText`_ for docstrings can be found `here
218 Details about using `reStructuredText`_ for docstrings can be found `here
174 <http://epydoc.sourceforge.net/manual-othermarkup.html>`_.
219 <http://epydoc.sourceforge.net/manual-othermarkup.html>`_.
175
220
176 .. _Epydoc: http://epydoc.sourceforge.net/
221 .. _Epydoc: http://epydoc.sourceforge.net/
177
222
178 Additional PEPs of interest regarding documentation of code:
223 Additional PEPs of interest regarding documentation of code:
179
224
180 - `Docstring Conventions <http://www.python.org/peps/pep-0257.html>`_
225 - `Docstring Conventions <http://www.python.org/peps/pep-0257.html>`_
181 - `Docstring Processing System Framework <http://www.python.org/peps/pep-0256.html>`_
226 - `Docstring Processing System Framework <http://www.python.org/peps/pep-0256.html>`_
182 - `Docutils Design Specification <http://www.python.org/peps/pep-0258.html>`_
227 - `Docutils Design Specification <http://www.python.org/peps/pep-0258.html>`_
183
228
184
229
185 Coding conventions
230 Coding conventions
186 ==================
231 ==================
187
232
188 General
233 General
189 -------
234 -------
190
235
191 In general, we'll try to follow the standard Python style conventions as described here:
236 In general, we'll try to follow the standard Python style conventions as described here:
192
237
193 - `Style Guide for Python Code <http://www.python.org/peps/pep-0008.html>`_
238 - `Style Guide for Python Code <http://www.python.org/peps/pep-0008.html>`_
194
239
195
240
196 Other comments:
241 Other comments:
197
242
198 - In a large file, top level classes and functions should be
243 - In a large file, top level classes and functions should be
199 separated by 2-3 lines to make it easier to separate them visually.
244 separated by 2-3 lines to make it easier to separate them visually.
200 - Use 4 spaces for indentation.
245 - Use 4 spaces for indentation.
201 - Keep the ordering of methods the same in classes that have the same
246 - Keep the ordering of methods the same in classes that have the same
202 methods. This is particularly true for classes that implement
247 methods. This is particularly true for classes that implement
203 similar interfaces and for interfaces that are similar.
248 similar interfaces and for interfaces that are similar.
204
249
205 Naming conventions
250 Naming conventions
206 ------------------
251 ------------------
207
252
208 In terms of naming conventions, we'll follow the guidelines from the `Style Guide for
253 In terms of naming conventions, we'll follow the guidelines from the `Style Guide for
209 Python Code`_.
254 Python Code`_.
210
255
211 For all new IPython code (and much existing code is being refactored), we'll use:
256 For all new IPython code (and much existing code is being refactored), we'll use:
212
257
213 - All ``lowercase`` module names.
258 - All ``lowercase`` module names.
214
259
215 - ``CamelCase`` for class names.
260 - ``CamelCase`` for class names.
216
261
217 - ``lowercase_with_underscores`` for methods, functions, variables and attributes.
262 - ``lowercase_with_underscores`` for methods, functions, variables and attributes.
218
263
219 This may be confusing as most of the existing IPython codebase uses a different convention (``lowerCamelCase`` for methods and attributes). Slowly, we will move IPython over to the new
264 This may be confusing as most of the existing IPython codebase uses a different convention (``lowerCamelCase`` for methods and attributes). Slowly, we will move IPython over to the new
220 convention, providing shadow names for backward compatibility in public interfaces.
265 convention, providing shadow names for backward compatibility in public interfaces.
221
266
222 There are, however, some important exceptions to these rules. In some cases, IPython
267 There are, however, some important exceptions to these rules. In some cases, IPython
223 code will interface with packages (Twisted, Wx, Qt) that use other conventions. At some level this makes it impossible to adhere to our own standards at all times. In particular, when subclassing classes that use other naming conventions, you must follow their naming conventions. To deal with cases like this, we propose the following policy:
268 code will interface with packages (Twisted, Wx, Qt) that use other conventions. At some level this makes it impossible to adhere to our own standards at all times. In particular, when subclassing classes that use other naming conventions, you must follow their naming conventions. To deal with cases like this, we propose the following policy:
224
269
225 - If you are subclassing a class that uses different conventions, use its
270 - If you are subclassing a class that uses different conventions, use its
226 naming conventions throughout your subclass. Thus, if you are creating a
271 naming conventions throughout your subclass. Thus, if you are creating a
227 Twisted Protocol class, used Twisted's ``namingSchemeForMethodsAndAttributes.``
272 Twisted Protocol class, used Twisted's ``namingSchemeForMethodsAndAttributes.``
228
273
229 - All IPython's official interfaces should use our conventions. In some cases
274 - All IPython's official interfaces should use our conventions. In some cases
230 this will mean that you need to provide shadow names (first implement ``fooBar``
275 this will mean that you need to provide shadow names (first implement ``fooBar``
231 and then ``foo_bar = fooBar``). We want to avoid this at all costs, but it
276 and then ``foo_bar = fooBar``). We want to avoid this at all costs, but it
232 will probably be necessary at times. But, please use this sparingly!
277 will probably be necessary at times. But, please use this sparingly!
233
278
234 Implementation-specific *private* methods will use ``_single_underscore_prefix``.
279 Implementation-specific *private* methods will use ``_single_underscore_prefix``.
235 Names with a leading double underscore will *only* be used in special cases, as they
280 Names with a leading double underscore will *only* be used in special cases, as they
236 makes subclassing difficult (such names are not easily seen by child classes).
281 makes subclassing difficult (such names are not easily seen by child classes).
237
282
238 Occasionally some run-in lowercase names are used, but mostly for very short names or
283 Occasionally some run-in lowercase names are used, but mostly for very short names or
239 where we are implementing methods very similar to existing ones in a base class (like
284 where we are implementing methods very similar to existing ones in a base class (like
240 ``runlines()`` where ``runsource()`` and ``runcode()`` had established precedent).
285 ``runlines()`` where ``runsource()`` and ``runcode()`` had established precedent).
241
286
242 The old IPython codebase has a big mix of classes and modules prefixed with an
287 The old IPython codebase has a big mix of classes and modules prefixed with an
243 explicit ``IP``. In Python this is mostly unnecessary, redundant and frowned upon, as
288 explicit ``IP``. In Python this is mostly unnecessary, redundant and frowned upon, as
244 namespaces offer cleaner prefixing. The only case where this approach is justified is
289 namespaces offer cleaner prefixing. The only case where this approach is justified is
245 for classes which are expected to be imported into external namespaces and a very
290 for classes which are expected to be imported into external namespaces and a very
246 generic name (like Shell) is too likely to clash with something else. We'll need to
291 generic name (like Shell) is too likely to clash with something else. We'll need to
247 revisit this issue as we clean up and refactor the code, but in general we should
292 revisit this issue as we clean up and refactor the code, but in general we should
248 remove as many unnecessary ``IP``/``ip`` prefixes as possible. However, if a prefix
293 remove as many unnecessary ``IP``/``ip`` prefixes as possible. However, if a prefix
249 seems absolutely necessary the more specific ``IPY`` or ``ipy`` are preferred.
294 seems absolutely necessary the more specific ``IPY`` or ``ipy`` are preferred.
250
295
251 .. _devel_testing:
296 .. _devel_testing:
252
297
253 Testing system
298 Testing system
254 ==============
299 ==============
255
300
256 It is extremely important that all code contributed to IPython has tests. Tests should
301 It is extremely important that all code contributed to IPython has tests. Tests should
257 be written as unittests, doctests or as entities that the `Nose`_ testing package will
302 be written as unittests, doctests or as entities that the `Nose`_ testing package will
258 find. Regardless of how the tests are written, we will use `Nose`_ for discovering and
303 find. Regardless of how the tests are written, we will use `Nose`_ for discovering and
259 running the tests. `Nose`_ will be required to run the IPython test suite, but will
304 running the tests. `Nose`_ will be required to run the IPython test suite, but will
260 not be required to simply use IPython.
305 not be required to simply use IPython.
261
306
262 .. _Nose: http://code.google.com/p/python-nose/
307 .. _Nose: http://code.google.com/p/python-nose/
263
308
264 Tests of `Twisted`__ using code should be written by subclassing the ``TestCase`` class
309 Tests of `Twisted`__ using code should be written by subclassing the ``TestCase`` class
265 that comes with ``twisted.trial.unittest``. When this is done, `Nose`_ will be able to
310 that comes with ``twisted.trial.unittest``. When this is done, `Nose`_ will be able to
266 run the tests and the twisted reactor will be handled correctly.
311 run the tests and the twisted reactor will be handled correctly.
267
312
268 .. __: http://www.twistedmatrix.com
313 .. __: http://www.twistedmatrix.com
269
314
270 Each subpackage in IPython should have its own ``tests`` directory that contains all
315 Each subpackage in IPython should have its own ``tests`` directory that contains all
271 of the tests for that subpackage. This allows each subpackage to be self-contained. If
316 of the tests for that subpackage. This allows each subpackage to be self-contained. If
272 a subpackage has any dependencies beyond the Python standard library, the tests for
317 a subpackage has any dependencies beyond the Python standard library, the tests for
273 that subpackage should be skipped if the dependencies are not found. This is very
318 that subpackage should be skipped if the dependencies are not found. This is very
274 important so users don't get tests failing simply because they don't have dependencies.
319 important so users don't get tests failing simply because they don't have dependencies.
275
320
276 We also need to look into use Noses ability to tag tests to allow a more modular
321 We also need to look into use Noses ability to tag tests to allow a more modular
277 approach of running tests.
322 approach of running tests.
278
323
279 .. _devel_config:
324 .. _devel_config:
280
325
281 Configuration system
326 Configuration system
282 ====================
327 ====================
283
328
284 IPython uses `.ini`_ files for configuration purposes. This represents a huge
329 IPython uses `.ini`_ files for configuration purposes. This represents a huge
285 improvement over the configuration system used in IPython. IPython works with these
330 improvement over the configuration system used in IPython. IPython works with these
286 files using the `ConfigObj`_ package, which IPython includes as
331 files using the `ConfigObj`_ package, which IPython includes as
287 ``ipython1/external/configobj.py``.
332 ``ipython1/external/configobj.py``.
288
333
289 Currently, we are using raw `ConfigObj`_ objects themselves. Each subpackage of IPython
334 Currently, we are using raw `ConfigObj`_ objects themselves. Each subpackage of IPython
290 should contain a ``config`` subdirectory that contains all of the configuration
335 should contain a ``config`` subdirectory that contains all of the configuration
291 information for the subpackage. To see how configuration information is defined (along
336 information for the subpackage. To see how configuration information is defined (along
292 with defaults) see at the examples in ``ipython1/kernel/config`` and
337 with defaults) see at the examples in ``ipython1/kernel/config`` and
293 ``ipython1/core/config``. Likewise, to see how the configuration information is used,
338 ``ipython1/core/config``. Likewise, to see how the configuration information is used,
294 see examples in ``ipython1/kernel/scripts/ipengine.py``.
339 see examples in ``ipython1/kernel/scripts/ipengine.py``.
295
340
296 Eventually, we will add a new layer on top of the raw `ConfigObj`_ objects. We are
341 Eventually, we will add a new layer on top of the raw `ConfigObj`_ objects. We are
297 calling this new layer, ``tconfig``, as it will use a `Traits`_-like validation model.
342 calling this new layer, ``tconfig``, as it will use a `Traits`_-like validation model.
298 We won't actually use `Traits`_, but will implement something similar in pure Python.
343 We won't actually use `Traits`_, but will implement something similar in pure Python.
299 But, even in this new system, we will still use `ConfigObj`_ and `.ini`_ files
344 But, even in this new system, we will still use `ConfigObj`_ and `.ini`_ files
300 underneath the hood. Talk to Fernando if you are interested in working on this part of
345 underneath the hood. Talk to Fernando if you are interested in working on this part of
301 IPython. The current prototype of ``tconfig`` is located in the IPython sandbox.
346 IPython. The current prototype of ``tconfig`` is located in the IPython sandbox.
302
347
303 .. _.ini: http://docs.python.org/lib/module-ConfigParser.html
348 .. _.ini: http://docs.python.org/lib/module-ConfigParser.html
304 .. _ConfigObj: http://www.voidspace.org.uk/python/configobj.html
349 .. _ConfigObj: http://www.voidspace.org.uk/python/configobj.html
305 .. _Traits: http://code.enthought.com/traits/
350 .. _Traits: http://code.enthought.com/traits/
306
351
307
352
308
353
309
354
310
355
311
356
312
357
313
358
314
359
315
360
@@ -1,226 +1,228 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """
3 """
4 This module defines the things that are used in setup.py for building IPython
4 This module defines the things that are used in setup.py for building IPython
5
5
6 This includes:
6 This includes:
7
7
8 * The basic arguments to setup
8 * The basic arguments to setup
9 * Functions for finding things like packages, package data, etc.
9 * Functions for finding things like packages, package data, etc.
10 * A function for checking dependencies.
10 * A function for checking dependencies.
11 """
11 """
12
12
13 __docformat__ = "restructuredtext en"
13 __docformat__ = "restructuredtext en"
14
14
15 #-------------------------------------------------------------------------------
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008 The IPython Development Team
16 # Copyright (C) 2008 The IPython Development Team
17 #
17 #
18 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21
21
22 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-------------------------------------------------------------------------------
24 #-------------------------------------------------------------------------------
25
25
26 import os, sys
26 import os, sys
27
27
28 from glob import glob
28 from glob import glob
29
29
30 from setupext import install_data_ext
30 from setupext import install_data_ext
31
31
32 #-------------------------------------------------------------------------------
32 #-------------------------------------------------------------------------------
33 # Useful globals and utility functions
33 # Useful globals and utility functions
34 #-------------------------------------------------------------------------------
34 #-------------------------------------------------------------------------------
35
35
36 # A few handy globals
36 # A few handy globals
37 isfile = os.path.isfile
37 isfile = os.path.isfile
38 pjoin = os.path.join
38 pjoin = os.path.join
39
39
40 def oscmd(s):
40 def oscmd(s):
41 print ">", s
41 print ">", s
42 os.system(s)
42 os.system(s)
43
43
44 # A little utility we'll need below, since glob() does NOT allow you to do
44 # A little utility we'll need below, since glob() does NOT allow you to do
45 # exclusion on multiple endings!
45 # exclusion on multiple endings!
46 def file_doesnt_endwith(test,endings):
46 def file_doesnt_endwith(test,endings):
47 """Return true if test is a file and its name does NOT end with any
47 """Return true if test is a file and its name does NOT end with any
48 of the strings listed in endings."""
48 of the strings listed in endings."""
49 if not isfile(test):
49 if not isfile(test):
50 return False
50 return False
51 for e in endings:
51 for e in endings:
52 if test.endswith(e):
52 if test.endswith(e):
53 return False
53 return False
54 return True
54 return True
55
55
56 #---------------------------------------------------------------------------
56 #---------------------------------------------------------------------------
57 # Basic project information
57 # Basic project information
58 #---------------------------------------------------------------------------
58 #---------------------------------------------------------------------------
59
59
60 # Release.py contains version, authors, license, url, keywords, etc.
60 # Release.py contains version, authors, license, url, keywords, etc.
61 execfile(pjoin('IPython','Release.py'))
61 execfile(pjoin('IPython','Release.py'))
62
62
63 # Create a dict with the basic information
63 # Create a dict with the basic information
64 # This dict is eventually passed to setup after additional keys are added.
64 # This dict is eventually passed to setup after additional keys are added.
65 setup_args = dict(
65 setup_args = dict(
66 name = name,
66 name = name,
67 version = version,
67 version = version,
68 description = description,
68 description = description,
69 long_description = long_description,
69 long_description = long_description,
70 author = author,
70 author = author,
71 author_email = author_email,
71 author_email = author_email,
72 url = url,
72 url = url,
73 download_url = download_url,
73 download_url = download_url,
74 license = license,
74 license = license,
75 platforms = platforms,
75 platforms = platforms,
76 keywords = keywords,
76 keywords = keywords,
77 cmdclass = {'install_data': install_data_ext},
77 cmdclass = {'install_data': install_data_ext},
78 )
78 )
79
79
80
80
81 #---------------------------------------------------------------------------
81 #---------------------------------------------------------------------------
82 # Find packages
82 # Find packages
83 #---------------------------------------------------------------------------
83 #---------------------------------------------------------------------------
84
84
85 def add_package(packages, pname, config=False, tests=False, scripts=False, others=None):
85 def add_package(packages, pname, config=False, tests=False, scripts=False, others=None):
86 """
86 """
87 Add a package to the list of packages, including certain subpackages.
87 Add a package to the list of packages, including certain subpackages.
88 """
88 """
89 packages.append('.'.join(['IPython',pname]))
89 packages.append('.'.join(['IPython',pname]))
90 if config:
90 if config:
91 packages.append('.'.join(['IPython',pname,'config']))
91 packages.append('.'.join(['IPython',pname,'config']))
92 if tests:
92 if tests:
93 packages.append('.'.join(['IPython',pname,'tests']))
93 packages.append('.'.join(['IPython',pname,'tests']))
94 if scripts:
94 if scripts:
95 packages.append('.'.join(['IPython',pname,'scripts']))
95 packages.append('.'.join(['IPython',pname,'scripts']))
96 if others is not None:
96 if others is not None:
97 for o in others:
97 for o in others:
98 packages.append('.'.join(['IPython',pname,o]))
98 packages.append('.'.join(['IPython',pname,o]))
99
99
100 def find_packages():
100 def find_packages():
101 """
101 """
102 Find all of IPython's packages.
102 Find all of IPython's packages.
103 """
103 """
104 packages = ['IPython']
104 packages = ['IPython']
105 add_package(packages, 'config', tests=True)
105 add_package(packages, 'config', tests=True)
106 add_package(packages , 'Extensions')
106 add_package(packages , 'Extensions')
107 add_package(packages, 'external')
107 add_package(packages, 'external')
108 add_package(packages, 'gui')
108 add_package(packages, 'gui')
109 add_package(packages, 'gui.wx')
109 add_package(packages, 'gui.wx')
110 add_package(packages, 'kernel', config=True, tests=True, scripts=True)
110 add_package(packages, 'kernel', config=True, tests=True, scripts=True)
111 add_package(packages, 'kernel.core', config=True, tests=True)
111 add_package(packages, 'kernel.core', config=True, tests=True)
112 add_package(packages, 'testing', tests=True)
112 add_package(packages, 'testing', tests=True)
113 add_package(packages, 'tools', tests=True)
113 add_package(packages, 'tools', tests=True)
114 add_package(packages, 'UserConfig')
114 add_package(packages, 'UserConfig')
115 return packages
115 return packages
116
116
117 #---------------------------------------------------------------------------
117 #---------------------------------------------------------------------------
118 # Find package data
118 # Find package data
119 #---------------------------------------------------------------------------
119 #---------------------------------------------------------------------------
120
120
121 def find_package_data():
121 def find_package_data():
122 """
122 """
123 Find IPython's package_data.
123 Find IPython's package_data.
124 """
124 """
125 # This is not enough for these things to appear in an sdist.
125 # This is not enough for these things to appear in an sdist.
126 # We need to muck with the MANIFEST to get this to work
126 # We need to muck with the MANIFEST to get this to work
127 package_data = {'IPython.UserConfig' : ['*'] }
127 package_data = {'IPython.UserConfig' : ['*'] }
128 return package_data
128 return package_data
129
129
130
130
131 #---------------------------------------------------------------------------
131 #---------------------------------------------------------------------------
132 # Find data files
132 # Find data files
133 #---------------------------------------------------------------------------
133 #---------------------------------------------------------------------------
134
134
135 def find_data_files():
135 def find_data_files():
136 """
136 """
137 Find IPython's data_files.
137 Find IPython's data_files.
138 """
138 """
139
139
140 # I can't find how to make distutils create a nested dir. structure, so
140 # I can't find how to make distutils create a nested dir. structure, so
141 # in the meantime do it manually. Butt ugly.
141 # in the meantime do it manually. Butt ugly.
142 # Note that http://www.redbrick.dcu.ie/~noel/distutils.html, ex. 2/3, contain
142 # Note that http://www.redbrick.dcu.ie/~noel/distutils.html, ex. 2/3, contain
143 # information on how to do this more cleanly once python 2.4 can be assumed.
143 # information on how to do this more cleanly once python 2.4 can be assumed.
144 # Thanks to Noel for the tip.
144 # Thanks to Noel for the tip.
145 docdirbase = 'share/doc/ipython'
145 docdirbase = 'share/doc/ipython'
146 manpagebase = 'share/man/man1'
146 manpagebase = 'share/man/man1'
147
147
148 # We only need to exclude from this things NOT already excluded in the
148 # We only need to exclude from this things NOT already excluded in the
149 # MANIFEST.in file.
149 # MANIFEST.in file.
150 exclude = ('.sh','.1.gz')
150 exclude = ('.sh','.1.gz')
151 # We need to figure out how we want to package all of our rst docs?
151 # We need to figure out how we want to package all of our rst docs?
152 # docfiles = filter(lambda f:file_doesnt_endwith(f,exclude),glob('docs/*'))
152 # docfiles = filter(lambda f:file_doesnt_endwith(f,exclude),glob('docs/*'))
153 examfiles = filter(isfile, glob('docs/examples/core/*.py'))
153 examfiles = filter(isfile, glob('docs/examples/core/*.py'))
154 examfiles.append(filter(isfile, glob('docs/examples/kernel/*.py')))
154 examfiles.append(filter(isfile, glob('docs/examples/kernel/*.py')))
155 manpages = filter(isfile, glob('docs/man/*.1.gz'))
155 manpages = filter(isfile, glob('docs/man/*.1.gz'))
156 igridhelpfiles = filter(isfile, glob('IPython/Extensions/igrid_help.*'))
156 igridhelpfiles = filter(isfile, glob('IPython/Extensions/igrid_help.*'))
157
157
158 data_files = [#('data', docdirbase, docfiles),
158 data_files = [#('data', docdirbase, docfiles),
159 ('data', pjoin(docdirbase, 'examples'),examfiles),
159 ('data', pjoin(docdirbase, 'examples'),examfiles),
160 ('data', manpagebase, manpages),
160 ('data', manpagebase, manpages),
161 ('data',pjoin(docdirbase, 'extensions'),igridhelpfiles),
161 ('data',pjoin(docdirbase, 'extensions'),igridhelpfiles),
162 ]
162 ]
163 return data_files
163 # import pprint
164 # pprint.pprint(data_files)
165 return []
164
166
165 #---------------------------------------------------------------------------
167 #---------------------------------------------------------------------------
166 # Find scripts
168 # Find scripts
167 #---------------------------------------------------------------------------
169 #---------------------------------------------------------------------------
168
170
169 def find_scripts():
171 def find_scripts():
170 """
172 """
171 Find IPython's scripts.
173 Find IPython's scripts.
172 """
174 """
173 scripts = []
175 scripts = []
174 scripts.append('IPython/kernel/scripts/ipengine')
176 scripts.append('IPython/kernel/scripts/ipengine')
175 scripts.append('IPython/kernel/scripts/ipcontroller')
177 scripts.append('IPython/kernel/scripts/ipcontroller')
176 scripts.append('IPython/kernel/scripts/ipcluster')
178 scripts.append('IPython/kernel/scripts/ipcluster')
177 scripts.append('scripts/ipython')
179 scripts.append('scripts/ipython')
178 scripts.append('scripts/pycolor')
180 scripts.append('scripts/pycolor')
179 scripts.append('scripts/irunner')
181 scripts.append('scripts/irunner')
180
182
181 # Script to be run by the windows binary installer after the default setup
183 # Script to be run by the windows binary installer after the default setup
182 # routine, to add shortcuts and similar windows-only things. Windows
184 # routine, to add shortcuts and similar windows-only things. Windows
183 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
185 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
184 # doesn't find them.
186 # doesn't find them.
185 if 'bdist_wininst' in sys.argv:
187 if 'bdist_wininst' in sys.argv:
186 if len(sys.argv) > 2 and ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
188 if len(sys.argv) > 2 and ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
187 print >> sys.stderr,"ERROR: bdist_wininst must be run alone. Exiting."
189 print >> sys.stderr,"ERROR: bdist_wininst must be run alone. Exiting."
188 sys.exit(1)
190 sys.exit(1)
189 scripts.append('scripts/ipython_win_post_install.py')
191 scripts.append('scripts/ipython_win_post_install.py')
190
192
191 return scripts
193 return scripts
192
194
193 #---------------------------------------------------------------------------
195 #---------------------------------------------------------------------------
194 # Find scripts
196 # Find scripts
195 #---------------------------------------------------------------------------
197 #---------------------------------------------------------------------------
196
198
197 def check_for_dependencies():
199 def check_for_dependencies():
198 """Check for IPython's dependencies.
200 """Check for IPython's dependencies.
199
201
200 This function should NOT be called if running under setuptools!
202 This function should NOT be called if running under setuptools!
201 """
203 """
202 from setupext.setupext import (
204 from setupext.setupext import (
203 print_line, print_raw, print_status, print_message,
205 print_line, print_raw, print_status, print_message,
204 check_for_zopeinterface, check_for_twisted,
206 check_for_zopeinterface, check_for_twisted,
205 check_for_foolscap, check_for_pyopenssl,
207 check_for_foolscap, check_for_pyopenssl,
206 check_for_sphinx, check_for_pygments,
208 check_for_sphinx, check_for_pygments,
207 check_for_nose, check_for_pexpect
209 check_for_nose, check_for_pexpect
208 )
210 )
209 print_line()
211 print_line()
210 print_raw("BUILDING IPYTHON")
212 print_raw("BUILDING IPYTHON")
211 print_status('python', sys.version)
213 print_status('python', sys.version)
212 print_status('platform', sys.platform)
214 print_status('platform', sys.platform)
213 if sys.platform == 'win32':
215 if sys.platform == 'win32':
214 print_status('Windows version', sys.getwindowsversion())
216 print_status('Windows version', sys.getwindowsversion())
215
217
216 print_raw("")
218 print_raw("")
217 print_raw("OPTIONAL DEPENDENCIES")
219 print_raw("OPTIONAL DEPENDENCIES")
218
220
219 check_for_zopeinterface()
221 check_for_zopeinterface()
220 check_for_twisted()
222 check_for_twisted()
221 check_for_foolscap()
223 check_for_foolscap()
222 check_for_pyopenssl()
224 check_for_pyopenssl()
223 check_for_sphinx()
225 check_for_sphinx()
224 check_for_pygments()
226 check_for_pygments()
225 check_for_nose()
227 check_for_nose()
226 check_for_pexpect() No newline at end of file
228 check_for_pexpect()
General Comments 0
You need to be logged in to leave comments. Login now