Show More
@@ -1,386 +1,391 b'' | |||
|
1 | 1 | |
|
2 | 2 | """ Implementations for various useful completers |
|
3 | 3 | |
|
4 | 4 | See Extensions/ipy_stock_completers.py on examples of how to enable a completer, |
|
5 | 5 | but the basic idea is to do: |
|
6 | 6 | |
|
7 | 7 | ip.set_hook('complete_command', svn_completer, str_key = 'svn') |
|
8 | 8 | |
|
9 | 9 | """ |
|
10 | 10 | import IPython.ipapi |
|
11 | 11 | import glob,os,shlex,sys |
|
12 | 12 | import inspect |
|
13 | 13 | from time import time |
|
14 | 14 | from zipimport import zipimporter |
|
15 | 15 | ip = IPython.ipapi.get() |
|
16 | 16 | |
|
17 | 17 | try: |
|
18 | 18 | set |
|
19 | 19 | except: |
|
20 | 20 | from sets import Set as set |
|
21 | 21 | |
|
22 | 22 | TIMEOUT_STORAGE = 3 #Time in seconds after which the rootmodules will be stored |
|
23 | 23 | TIMEOUT_GIVEUP = 20 #Time in seconds after which we give up |
|
24 | 24 | |
|
25 | 25 | def quick_completer(cmd, completions): |
|
26 | 26 | """ Easily create a trivial completer for a command. |
|
27 | 27 | |
|
28 | 28 | Takes either a list of completions, or all completions in string |
|
29 | 29 | (that will be split on whitespace) |
|
30 | 30 | |
|
31 | 31 | Example:: |
|
32 | 32 | |
|
33 | 33 | [d:\ipython]|1> import ipy_completers |
|
34 | 34 | [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz']) |
|
35 | 35 | [d:\ipython]|3> foo b<TAB> |
|
36 | 36 | bar baz |
|
37 | 37 | [d:\ipython]|3> foo ba |
|
38 | 38 | """ |
|
39 | 39 | if isinstance(completions, basestring): |
|
40 | 40 | |
|
41 | 41 | completions = completions.split() |
|
42 | 42 | def do_complete(self,event): |
|
43 | 43 | return completions |
|
44 | 44 | |
|
45 | 45 | ip.set_hook('complete_command',do_complete, str_key = cmd) |
|
46 | 46 | |
|
47 | 47 | def getRootModules(): |
|
48 | 48 | """ |
|
49 | 49 | Returns a list containing the names of all the modules available in the |
|
50 | 50 | folders of the pythonpath. |
|
51 | 51 | """ |
|
52 | 52 | modules = [] |
|
53 | 53 | if ip.db.has_key('rootmodules'): |
|
54 | 54 | return ip.db['rootmodules'] |
|
55 | 55 | t = time() |
|
56 | 56 | store = False |
|
57 | 57 | for path in sys.path: |
|
58 | 58 | modules += moduleList(path) |
|
59 | 59 | if time() - t >= TIMEOUT_STORAGE and not store: |
|
60 | 60 | store = True |
|
61 | 61 | print "\nCaching the list of root modules, please wait!" |
|
62 | 62 | print "(This will only be done once - type '%rehashx' to " + \ |
|
63 | 63 | "reset cache!)" |
|
64 | 64 | |
|
65 | 65 | if time() - t > TIMEOUT_GIVEUP: |
|
66 | 66 | print "This is taking too long, we give up." |
|
67 | 67 | |
|
68 | 68 | ip.db['rootmodules'] = [] |
|
69 | 69 | return [] |
|
70 | 70 | |
|
71 | 71 | modules += sys.builtin_module_names |
|
72 | 72 | |
|
73 | 73 | modules = list(set(modules)) |
|
74 | 74 | if '__init__' in modules: |
|
75 | 75 | modules.remove('__init__') |
|
76 | 76 | modules = list(set(modules)) |
|
77 | 77 | if store: |
|
78 | 78 | ip.db['rootmodules'] = modules |
|
79 | 79 | return modules |
|
80 | 80 | |
|
81 | 81 | def moduleList(path): |
|
82 | 82 | """ |
|
83 | 83 | Return the list containing the names of the modules available in the given |
|
84 | 84 | folder. |
|
85 | 85 | """ |
|
86 | 86 | |
|
87 | 87 | if os.path.isdir(path): |
|
88 | 88 | folder_list = os.listdir(path) |
|
89 | 89 | elif path.endswith('.egg'): |
|
90 | 90 | try: |
|
91 | 91 | folder_list = [f for f in zipimporter(path)._files] |
|
92 | 92 | except: |
|
93 | 93 | folder_list = [] |
|
94 | 94 | else: |
|
95 | 95 | folder_list = [] |
|
96 | 96 | #folder_list = glob.glob(os.path.join(path,'*')) |
|
97 | 97 | folder_list = [p for p in folder_list \ |
|
98 | 98 | if os.path.exists(os.path.join(path, p,'__init__.py'))\ |
|
99 | 99 | or p[-3:] in ('.py','.so')\ |
|
100 | 100 | or p[-4:] in ('.pyc','.pyo','.pyd')] |
|
101 | 101 | |
|
102 | 102 | folder_list = [os.path.basename(p).split('.')[0] for p in folder_list] |
|
103 | 103 | return folder_list |
|
104 | 104 | |
|
105 | 105 | def moduleCompletion(line): |
|
106 | 106 | """ |
|
107 | 107 | Returns a list containing the completion possibilities for an import line. |
|
108 | 108 | The line looks like this : |
|
109 | 109 | 'import xml.d' |
|
110 | 110 | 'from xml.dom import' |
|
111 | 111 | """ |
|
112 | 112 | def tryImport(mod, only_modules=False): |
|
113 | 113 | def isImportable(module, attr): |
|
114 | 114 | if only_modules: |
|
115 | 115 | return inspect.ismodule(getattr(module, attr)) |
|
116 | 116 | else: |
|
117 | 117 | return not(attr[:2] == '__' and attr[-2:] == '__') |
|
118 | 118 | try: |
|
119 | 119 | m = __import__(mod) |
|
120 | 120 | except: |
|
121 | 121 | return [] |
|
122 | 122 | mods = mod.split('.') |
|
123 | 123 | for module in mods[1:]: |
|
124 | 124 | m = getattr(m,module) |
|
125 | 125 | if (not hasattr(m, '__file__')) or (not only_modules) or\ |
|
126 | 126 | (hasattr(m, '__file__') and '__init__' in m.__file__): |
|
127 | 127 | completion_list = [attr for attr in dir(m) if isImportable(m, attr)] |
|
128 | 128 | completion_list.extend(getattr(m,'__all__',[])) |
|
129 | 129 | if hasattr(m, '__file__') and '__init__' in m.__file__: |
|
130 | 130 | completion_list.extend(moduleList(os.path.dirname(m.__file__))) |
|
131 | 131 | completion_list = list(set(completion_list)) |
|
132 | 132 | if '__init__' in completion_list: |
|
133 | 133 | completion_list.remove('__init__') |
|
134 | 134 | return completion_list |
|
135 | 135 | |
|
136 | 136 | words = line.split(' ') |
|
137 | 137 | if len(words) == 3 and words[0] == 'from': |
|
138 | 138 | return ['import '] |
|
139 | 139 | if len(words) < 3 and (words[0] in ['import','from']) : |
|
140 | 140 | if len(words) == 1: |
|
141 | 141 | return getRootModules() |
|
142 | 142 | mod = words[1].split('.') |
|
143 | 143 | if len(mod) < 2: |
|
144 | 144 | return getRootModules() |
|
145 | 145 | completion_list = tryImport('.'.join(mod[:-1]), True) |
|
146 | 146 | completion_list = ['.'.join(mod[:-1] + [el]) for el in completion_list] |
|
147 | 147 | return completion_list |
|
148 | 148 | if len(words) >= 3 and words[0] == 'from': |
|
149 | 149 | mod = words[1] |
|
150 | 150 | return tryImport(mod) |
|
151 | 151 | |
|
152 | 152 | def vcs_completer(commands, event): |
|
153 | 153 | """ utility to make writing typical version control app completers easier |
|
154 | 154 | |
|
155 | 155 | VCS command line apps typically have the format: |
|
156 | 156 | |
|
157 | 157 | [sudo ]PROGNAME [help] [command] file file... |
|
158 | 158 | |
|
159 | 159 | """ |
|
160 | 160 | |
|
161 | 161 | |
|
162 | 162 | cmd_param = event.line.split() |
|
163 | 163 | if event.line.endswith(' '): |
|
164 | 164 | cmd_param.append('') |
|
165 | 165 | |
|
166 | 166 | if cmd_param[0] == 'sudo': |
|
167 | 167 | cmd_param = cmd_param[1:] |
|
168 | 168 | |
|
169 | 169 | if len(cmd_param) == 2 or 'help' in cmd_param: |
|
170 | 170 | return commands.split() |
|
171 | 171 | |
|
172 | 172 | return ip.IP.Completer.file_matches(event.symbol) |
|
173 | 173 | |
|
174 | 174 | |
|
175 | 175 | pkg_cache = None |
|
176 | 176 | |
|
177 | 177 | def module_completer(self,event): |
|
178 | 178 | """ Give completions after user has typed 'import ...' or 'from ...'""" |
|
179 | 179 | |
|
180 | 180 | # This works in all versions of python. While 2.5 has |
|
181 | 181 | # pkgutil.walk_packages(), that particular routine is fairly dangerous, |
|
182 | 182 | # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full |
|
183 | 183 | # of possibly problematic side effects. |
|
184 | 184 | # This search the folders in the sys.path for available modules. |
|
185 | 185 | |
|
186 | 186 | return moduleCompletion(event.line) |
|
187 | 187 | |
|
188 | 188 | |
|
189 | 189 | svn_commands = """\ |
|
190 | 190 | add blame praise annotate ann cat checkout co cleanup commit ci copy |
|
191 | 191 | cp delete del remove rm diff di export help ? h import info list ls |
|
192 | 192 | lock log merge mkdir move mv rename ren propdel pdel pd propedit pedit |
|
193 | 193 | pe propget pget pg proplist plist pl propset pset ps resolved revert |
|
194 | 194 | status stat st switch sw unlock update |
|
195 | 195 | """ |
|
196 | 196 | |
|
197 | 197 | def svn_completer(self,event): |
|
198 | 198 | return vcs_completer(svn_commands, event) |
|
199 | 199 | |
|
200 | 200 | |
|
201 | 201 | hg_commands = """ |
|
202 | 202 | add addremove annotate archive backout branch branches bundle cat |
|
203 | 203 | clone commit copy diff export grep heads help identify import incoming |
|
204 | 204 | init locate log manifest merge outgoing parents paths pull push |
|
205 | 205 | qapplied qclone qcommit qdelete qdiff qfold qguard qheader qimport |
|
206 | 206 | qinit qnew qnext qpop qprev qpush qrefresh qrename qrestore qsave |
|
207 | 207 | qselect qseries qtop qunapplied recover remove rename revert rollback |
|
208 | 208 | root serve showconfig status strip tag tags tip unbundle update verify |
|
209 | 209 | version |
|
210 | 210 | """ |
|
211 | 211 | |
|
212 | 212 | def hg_completer(self,event): |
|
213 | 213 | """ Completer for mercurial commands """ |
|
214 | 214 | |
|
215 | 215 | return vcs_completer(hg_commands, event) |
|
216 | 216 | |
|
217 | 217 | |
|
218 | 218 | |
|
219 | 219 | __bzr_commands = None |
|
220 | 220 | |
|
221 | 221 | def bzr_commands(): |
|
222 | 222 | global __bzr_commands |
|
223 | 223 | if __bzr_commands is not None: |
|
224 | 224 | return __bzr_commands |
|
225 | 225 | out = os.popen('bzr help commands') |
|
226 | 226 | __bzr_commands = [l.split()[0] for l in out] |
|
227 | 227 | return __bzr_commands |
|
228 | 228 | |
|
229 | 229 | def bzr_completer(self,event): |
|
230 | 230 | """ Completer for bazaar commands """ |
|
231 | 231 | cmd_param = event.line.split() |
|
232 | 232 | if event.line.endswith(' '): |
|
233 | 233 | cmd_param.append('') |
|
234 | 234 | |
|
235 | 235 | if len(cmd_param) > 2: |
|
236 | 236 | cmd = cmd_param[1] |
|
237 | 237 | param = cmd_param[-1] |
|
238 | 238 | output_file = (param == '--output=') |
|
239 | 239 | if cmd == 'help': |
|
240 | 240 | return bzr_commands() |
|
241 | 241 | elif cmd in ['bundle-revisions','conflicts', |
|
242 | 242 | 'deleted','nick','register-branch', |
|
243 | 243 | 'serve','unbind','upgrade','version', |
|
244 | 244 | 'whoami'] and not output_file: |
|
245 | 245 | return [] |
|
246 | 246 | else: |
|
247 | 247 | # the rest are probably file names |
|
248 | 248 | return ip.IP.Completer.file_matches(event.symbol) |
|
249 | 249 | |
|
250 | 250 | return bzr_commands() |
|
251 | 251 | |
|
252 | 252 | |
|
253 | 253 | def shlex_split(x): |
|
254 | 254 | """Helper function to split lines into segments.""" |
|
255 | 255 | #shlex.split raise exception if syntax error in sh syntax |
|
256 | 256 | #for example if no closing " is found. This function keeps dropping |
|
257 | 257 | #the last character of the line until shlex.split does not raise |
|
258 | 258 | #exception. Adds end of the line to the result of shlex.split |
|
259 | 259 | #example: %run "c:/python -> ['%run','"c:/python'] |
|
260 | 260 | endofline=[] |
|
261 | 261 | while x!="": |
|
262 | 262 | try: |
|
263 | 263 | comps=shlex.split(x) |
|
264 | 264 | if len(endofline)>=1: |
|
265 | 265 | comps.append("".join(endofline)) |
|
266 | 266 | return comps |
|
267 | 267 | except ValueError: |
|
268 | 268 | endofline=[x[-1:]]+endofline |
|
269 | 269 | x=x[:-1] |
|
270 | 270 | return ["".join(endofline)] |
|
271 | 271 | |
|
272 | 272 | def runlistpy(self, event): |
|
273 | 273 | comps = shlex_split(event.line) |
|
274 | 274 | relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"") |
|
275 | 275 | |
|
276 | 276 | #print "\nev=",event # dbg |
|
277 | 277 | #print "rp=",relpath # dbg |
|
278 | 278 | #print 'comps=',comps # dbg |
|
279 | 279 | |
|
280 | 280 | lglob = glob.glob |
|
281 | 281 | isdir = os.path.isdir |
|
282 | 282 | if relpath.startswith('~'): |
|
283 | 283 | relpath = os.path.expanduser(relpath) |
|
284 | 284 | dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') |
|
285 | 285 | if isdir(f)] |
|
286 | 286 | |
|
287 | 287 | # Find if the user has already typed the first filename, after which we |
|
288 | 288 | # should complete on all files, since after the first one other files may |
|
289 | 289 | # be arguments to the input script. |
|
290 | 290 | #filter( |
|
291 | 291 | if filter(lambda f: f.endswith('.py') or f.endswith('.ipy') or |
|
292 | 292 | f.endswith('.pyw'),comps): |
|
293 | 293 | pys = [f.replace('\\','/') for f in lglob('*')] |
|
294 | 294 | else: |
|
295 | 295 | pys = [f.replace('\\','/') |
|
296 | 296 | for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') + |
|
297 | 297 | lglob(relpath + '*.pyw')] |
|
298 | 298 | return dirs + pys |
|
299 | 299 | |
|
300 | 300 | |
|
301 | greedy_cd_completer = False | |
|
302 | ||
|
301 | 303 | def cd_completer(self, event): |
|
302 | 304 | relpath = event.symbol |
|
303 | 305 | #print event # dbg |
|
304 | 306 | if '-b' in event.line: |
|
305 | 307 | # return only bookmark completions |
|
306 | 308 | bkms = self.db.get('bookmarks',{}) |
|
307 | 309 | return bkms.keys() |
|
308 | 310 | |
|
309 | 311 | |
|
310 | 312 | if event.symbol == '-': |
|
311 | 313 | width_dh = str(len(str(len(ip.user_ns['_dh']) + 1))) |
|
312 | 314 | # jump in directory history by number |
|
313 | 315 | fmt = '-%0' + width_dh +'d [%s]' |
|
314 | 316 | ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])] |
|
315 | 317 | if len(ents) > 1: |
|
316 | 318 | return ents |
|
317 | 319 | return [] |
|
318 | 320 | |
|
319 | 321 | if relpath.startswith('~'): |
|
320 | 322 | relpath = os.path.expanduser(relpath).replace('\\','/') |
|
321 | 323 | found = [] |
|
322 | 324 | for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*') |
|
323 | 325 | if os.path.isdir(f)]: |
|
324 | 326 | if ' ' in d: |
|
325 | 327 | # we don't want to deal with any of that, complex code |
|
326 | 328 | # for this is elsewhere |
|
327 | 329 | raise IPython.ipapi.TryNext |
|
328 | 330 | found.append( d ) |
|
329 | 331 | |
|
330 | 332 | if not found: |
|
331 | 333 | if os.path.isdir(relpath): |
|
332 | 334 | return [relpath] |
|
333 | 335 | raise IPython.ipapi.TryNext |
|
334 | 336 | |
|
335 | 337 | |
|
336 | 338 | def single_dir_expand(matches): |
|
337 | 339 | "Recursively expand match lists containing a single dir." |
|
338 | 340 | |
|
339 | 341 | if len(matches) == 1 and os.path.isdir(matches[0]): |
|
340 | 342 | # Takes care of links to directories also. Use '/' |
|
341 | 343 | # explicitly, even under Windows, so that name completions |
|
342 | 344 | # don't end up escaped. |
|
343 | 345 | d = matches[0] |
|
344 | 346 | if d[-1] in ['/','\\']: |
|
345 | 347 | d = d[:-1] |
|
346 | 348 | |
|
347 | 349 | subdirs = [p for p in os.listdir(d) if os.path.isdir( d + '/' + p) and not p.startswith('.')] |
|
348 | 350 | if subdirs: |
|
349 | 351 | matches = [ (d + '/' + p) for p in subdirs ] |
|
350 | 352 | return single_dir_expand(matches) |
|
351 | 353 | else: |
|
352 | 354 | return matches |
|
353 | 355 | else: |
|
354 | 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 | 363 | def apt_get_packages(prefix): |
|
359 | 364 | out = os.popen('apt-cache pkgnames') |
|
360 | 365 | for p in out: |
|
361 | 366 | if p.startswith(prefix): |
|
362 | 367 | yield p.rstrip() |
|
363 | 368 | |
|
364 | 369 | |
|
365 | 370 | apt_commands = """\ |
|
366 | 371 | update upgrade install remove purge source build-dep dist-upgrade |
|
367 | 372 | dselect-upgrade clean autoclean check""" |
|
368 | 373 | |
|
369 | 374 | def apt_completer(self, event): |
|
370 | 375 | """ Completer for apt-get (uses apt-cache internally) |
|
371 | 376 | |
|
372 | 377 | """ |
|
373 | 378 | |
|
374 | 379 | |
|
375 | 380 | cmd_param = event.line.split() |
|
376 | 381 | if event.line.endswith(' '): |
|
377 | 382 | cmd_param.append('') |
|
378 | 383 | |
|
379 | 384 | if cmd_param[0] == 'sudo': |
|
380 | 385 | cmd_param = cmd_param[1:] |
|
381 | 386 | |
|
382 | 387 | if len(cmd_param) == 2 or 'help' in cmd_param: |
|
383 | 388 | return apt_commands.split() |
|
384 | 389 | |
|
385 | 390 | return list(apt_get_packages(event.symbol)) |
|
386 | 391 |
@@ -1,251 +1,258 b'' | |||
|
1 | 1 | """Shell mode for IPython. |
|
2 | 2 | |
|
3 | 3 | Start ipython in shell mode by invoking "ipython -p sh" |
|
4 | 4 | |
|
5 | 5 | (the old version, "ipython -p pysh" still works but this is the more "modern" |
|
6 | 6 | shell mode and is recommended for users who don't care about pysh-mode |
|
7 | 7 | compatibility) |
|
8 | 8 | """ |
|
9 | 9 | |
|
10 | 10 | from IPython import ipapi |
|
11 | 11 | import os,textwrap |
|
12 | 12 | |
|
13 | 13 | # The import below effectively obsoletes your old-style ipythonrc[.ini], |
|
14 | 14 | # so consider yourself warned! |
|
15 | 15 | |
|
16 | 16 | import ipy_defaults |
|
17 | 17 | |
|
18 | 18 | def main(): |
|
19 | 19 | ip = ipapi.get() |
|
20 | 20 | o = ip.options |
|
21 | 21 | # autocall to "full" mode (smart mode is default, I like full mode) |
|
22 | 22 | |
|
23 | 23 | o.autocall = 2 |
|
24 | 24 | |
|
25 | 25 | # Jason Orendorff's path class is handy to have in user namespace |
|
26 | 26 | # if you are doing shell-like stuff |
|
27 | 27 | try: |
|
28 | 28 | ip.ex("from IPython.external.path import path" ) |
|
29 | 29 | except ImportError: |
|
30 | 30 | pass |
|
31 | 31 | |
|
32 | 32 | # beefed up %env is handy in shell mode |
|
33 | 33 | import envpersist |
|
34 | 34 | |
|
35 | 35 | # To see where mycmd resides (in path/aliases), do %which mycmd |
|
36 | 36 | import ipy_which |
|
37 | 37 | |
|
38 | 38 | # tab completers for hg, svn, ... |
|
39 | 39 | import ipy_app_completers |
|
40 | 40 | |
|
41 | 41 | # To make executables foo and bar in mybin usable without PATH change, do: |
|
42 | 42 | # %rehashdir c:/mybin |
|
43 | 43 | # %store foo |
|
44 | 44 | # %store bar |
|
45 | 45 | import ipy_rehashdir |
|
46 | 46 | |
|
47 | 47 | # does not work without subprocess module! |
|
48 | 48 | #import ipy_signals |
|
49 | 49 | |
|
50 | 50 | ip.ex('import os') |
|
51 | 51 | ip.ex("def up(): os.chdir('..')") |
|
52 | 52 | ip.user_ns['LA'] = LastArgFinder() |
|
53 | 53 | # Nice prompt |
|
54 | 54 | |
|
55 | 55 | o.prompt_in1= r'\C_LightBlue[\C_LightCyan\Y2\C_LightBlue]\C_Green|\#> ' |
|
56 | 56 | o.prompt_in2= r'\C_Green|\C_LightGreen\D\C_Green> ' |
|
57 | 57 | o.prompt_out= '<\#> ' |
|
58 | 58 | |
|
59 | 59 | from IPython import Release |
|
60 | 60 | |
|
61 | 61 | import sys |
|
62 | 62 | # Non-chatty banner |
|
63 | 63 | o.banner = "IPython %s [on Py %s]\n" % (Release.version,sys.version.split(None,1)[0]) |
|
64 | 64 | |
|
65 | 65 | |
|
66 | 66 | ip.IP.default_option('cd','-q') |
|
67 | 67 | ip.IP.default_option('macro', '-r') |
|
68 | 68 | # If you only rarely want to execute the things you %edit... |
|
69 | 69 | #ip.IP.default_option('edit','-x') |
|
70 | 70 | |
|
71 | 71 | |
|
72 | 72 | o.prompts_pad_left="1" |
|
73 | 73 | # Remove all blank lines in between prompts, like a normal shell. |
|
74 | 74 | o.separate_in="0" |
|
75 | 75 | o.separate_out="0" |
|
76 | 76 | o.separate_out2="0" |
|
77 | 77 | |
|
78 | 78 | # now alias all syscommands |
|
79 | 79 | |
|
80 | 80 | db = ip.db |
|
81 | 81 | |
|
82 | 82 | syscmds = db.get("syscmdlist",[] ) |
|
83 | 83 | if not syscmds: |
|
84 | 84 | print textwrap.dedent(""" |
|
85 | 85 | System command list not initialized, probably the first run... |
|
86 | 86 | running %rehashx to refresh the command list. Run %rehashx |
|
87 | 87 | again to refresh command list (after installing new software etc.) |
|
88 | 88 | """) |
|
89 | 89 | ip.magic('rehashx') |
|
90 | 90 | syscmds = db.get("syscmdlist") |
|
91 | 91 | |
|
92 | 92 | # lowcase aliases on win32 only |
|
93 | 93 | if os.name == 'posix': |
|
94 | 94 | mapper = lambda s:s |
|
95 | 95 | else: |
|
96 | 96 | def mapper(s): return s.lower() |
|
97 | 97 | |
|
98 | 98 | for cmd in syscmds: |
|
99 | 99 | # print "sys",cmd #dbg |
|
100 | 100 | noext, ext = os.path.splitext(cmd) |
|
101 | 101 | key = mapper(noext) |
|
102 | 102 | if key not in ip.IP.alias_table: |
|
103 | 103 | ip.defalias(key, cmd) |
|
104 | 104 | |
|
105 | 105 | # mglob combines 'find', recursion, exclusion... '%mglob?' to learn more |
|
106 | 106 | ip.load("IPython.external.mglob") |
|
107 | 107 | |
|
108 | 108 | # win32 is crippled w/o cygwin, try to help it a little bit |
|
109 | 109 | if sys.platform == 'win32': |
|
110 | 110 | if 'cygwin' in os.environ['PATH'].lower(): |
|
111 | 111 | # use the colors of cygwin ls (recommended) |
|
112 | 112 | ip.defalias('d', 'ls -F --color=auto') |
|
113 | 113 | else: |
|
114 | 114 | # get icp, imv, imkdir, igrep, irm,... |
|
115 | 115 | ip.load('ipy_fsops') |
|
116 | 116 | |
|
117 | 117 | # and the next best thing to real 'ls -F' |
|
118 | 118 | ip.defalias('d','dir /w /og /on') |
|
119 | 119 | |
|
120 | ip.set_hook('input_prefilter', dotslash_prefilter_f) | |
|
120 | 121 | extend_shell_behavior(ip) |
|
121 | 122 | |
|
122 | 123 | class LastArgFinder: |
|
123 | 124 | """ Allow $LA to work as "last argument of previous command", like $! in bash |
|
124 | 125 | |
|
125 | 126 | To call this in normal IPython code, do LA() |
|
126 | 127 | """ |
|
127 | 128 | def __call__(self, hist_idx = None): |
|
128 | 129 | ip = ipapi.get() |
|
129 | 130 | if hist_idx is None: |
|
130 | 131 | return str(self) |
|
131 | 132 | return ip.IP.input_hist_raw[hist_idx].strip().split()[-1] |
|
132 | 133 | def __str__(self): |
|
133 | 134 | ip = ipapi.get() |
|
134 | 135 | for cmd in reversed(ip.IP.input_hist_raw): |
|
135 | 136 | parts = cmd.strip().split() |
|
136 | 137 | if len(parts) < 2 or parts[-1] in ['$LA', 'LA()']: |
|
137 | 138 | continue |
|
138 | 139 | return parts[-1] |
|
139 | 140 | return "" |
|
140 | 141 | |
|
141 | ||
|
142 | ||
|
143 | ||
|
142 | def dotslash_prefilter_f(self,line): | |
|
143 | """ ./foo now runs foo as system command | |
|
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 | 152 | # XXX You do not need to understand the next function! |
|
146 | 153 | # This should probably be moved out of profile |
|
147 | 154 | |
|
148 | 155 | def extend_shell_behavior(ip): |
|
149 | 156 | |
|
150 | 157 | # Instead of making signature a global variable tie it to IPSHELL. |
|
151 | 158 | # In future if it is required to distinguish between different |
|
152 | 159 | # shells we can assign a signature per shell basis |
|
153 | 160 | ip.IP.__sig__ = 0xa005 |
|
154 | 161 | # mark the IPSHELL with this signature |
|
155 | 162 | ip.IP.user_ns['__builtins__'].__dict__['__sig__'] = ip.IP.__sig__ |
|
156 | 163 | |
|
157 | 164 | from IPython.Itpl import ItplNS |
|
158 | 165 | from IPython.genutils import shell |
|
159 | 166 | # utility to expand user variables via Itpl |
|
160 | 167 | # xxx do something sensible with depth? |
|
161 | 168 | ip.IP.var_expand = lambda cmd, lvars=None, depth=2: \ |
|
162 | 169 | str(ItplNS(cmd, ip.IP.user_ns, get_locals())) |
|
163 | 170 | |
|
164 | 171 | def get_locals(): |
|
165 | 172 | """ Substituting a variable through Itpl deep inside the IPSHELL stack |
|
166 | 173 | requires the knowledge of all the variables in scope upto the last |
|
167 | 174 | IPSHELL frame. This routine simply merges all the local variables |
|
168 | 175 | on the IPSHELL stack without worrying about their scope rules |
|
169 | 176 | """ |
|
170 | 177 | import sys |
|
171 | 178 | # note lambda expression constitues a function call |
|
172 | 179 | # hence fno should be incremented by one |
|
173 | 180 | getsig = lambda fno: sys._getframe(fno+1).f_globals \ |
|
174 | 181 | ['__builtins__'].__dict__['__sig__'] |
|
175 | 182 | getlvars = lambda fno: sys._getframe(fno+1).f_locals |
|
176 | 183 | # trackback until we enter the IPSHELL |
|
177 | 184 | frame_no = 1 |
|
178 | 185 | sig = ip.IP.__sig__ |
|
179 | 186 | fsig = ~sig |
|
180 | 187 | while fsig != sig : |
|
181 | 188 | try: |
|
182 | 189 | fsig = getsig(frame_no) |
|
183 | 190 | except (AttributeError, KeyError): |
|
184 | 191 | frame_no += 1 |
|
185 | 192 | except ValueError: |
|
186 | 193 | # stack is depleted |
|
187 | 194 | # call did not originate from IPSHELL |
|
188 | 195 | return {} |
|
189 | 196 | first_frame = frame_no |
|
190 | 197 | # walk further back until we exit from IPSHELL or deplete stack |
|
191 | 198 | try: |
|
192 | 199 | while(sig == getsig(frame_no+1)): |
|
193 | 200 | frame_no += 1 |
|
194 | 201 | except (AttributeError, KeyError, ValueError): |
|
195 | 202 | pass |
|
196 | 203 | # merge the locals from top down hence overriding |
|
197 | 204 | # any re-definitions of variables, functions etc. |
|
198 | 205 | lvars = {} |
|
199 | 206 | for fno in range(frame_no, first_frame-1, -1): |
|
200 | 207 | lvars.update(getlvars(fno)) |
|
201 | 208 | #print '\n'*5, first_frame, frame_no, '\n', lvars, '\n'*5 #dbg |
|
202 | 209 | return lvars |
|
203 | 210 | |
|
204 | 211 | def _runlines(lines): |
|
205 | 212 | """Run a string of one or more lines of source. |
|
206 | 213 | |
|
207 | 214 | This method is capable of running a string containing multiple source |
|
208 | 215 | lines, as if they had been entered at the IPython prompt. Since it |
|
209 | 216 | exposes IPython's processing machinery, the given strings can contain |
|
210 | 217 | magic calls (%magic), special shell access (!cmd), etc.""" |
|
211 | 218 | |
|
212 | 219 | # We must start with a clean buffer, in case this is run from an |
|
213 | 220 | # interactive IPython session (via a magic, for example). |
|
214 | 221 | ip.IP.resetbuffer() |
|
215 | 222 | lines = lines.split('\n') |
|
216 | 223 | more = 0 |
|
217 | 224 | command = '' |
|
218 | 225 | for line in lines: |
|
219 | 226 | # skip blank lines so we don't mess up the prompt counter, but do |
|
220 | 227 | # NOT skip even a blank line if we are in a code block (more is |
|
221 | 228 | # true) |
|
222 | 229 | # if command is not empty trim the line |
|
223 | 230 | if command != '' : |
|
224 | 231 | line = line.strip() |
|
225 | 232 | # add the broken line to the command |
|
226 | 233 | if line and line[-1] == '\\' : |
|
227 | 234 | command += line[0:-1] + ' ' |
|
228 | 235 | more = True |
|
229 | 236 | continue |
|
230 | 237 | else : |
|
231 | 238 | # add the last (current) line to the command |
|
232 | 239 | command += line |
|
233 | 240 | if command or more: |
|
234 | 241 | # push to raw history, so hist line numbers stay in sync |
|
235 | 242 | ip.IP.input_hist_raw.append("# " + command + "\n") |
|
236 | 243 | |
|
237 | 244 | more = ip.IP.push(ip.IP.prefilter(command,more)) |
|
238 | 245 | command = '' |
|
239 | 246 | # IPython's runsource returns None if there was an error |
|
240 | 247 | # compiling the code. This allows us to stop processing right |
|
241 | 248 | # away, so the user gets the error message at the right place. |
|
242 | 249 | if more is None: |
|
243 | 250 | break |
|
244 | 251 | # final newline in case the input didn't have it, so that the code |
|
245 | 252 | # actually does get executed |
|
246 | 253 | if more: |
|
247 | 254 | ip.IP.push('\n') |
|
248 | 255 | |
|
249 | 256 | ip.IP.runlines = _runlines |
|
250 | 257 | |
|
251 | 258 | main() |
@@ -1,107 +1,116 b'' | |||
|
1 | 1 | """ User configuration file for IPython |
|
2 | 2 | |
|
3 | 3 | This is a more flexible and safe way to configure ipython than *rc files |
|
4 | 4 | (ipythonrc, ipythonrc-pysh etc.) |
|
5 | 5 | |
|
6 | 6 | This file is always imported on ipython startup. You can import the |
|
7 | 7 | ipython extensions you need here (see IPython/Extensions directory). |
|
8 | 8 | |
|
9 | 9 | Feel free to edit this file to customize your ipython experience. |
|
10 | 10 | |
|
11 | 11 | Note that as such this file does nothing, for backwards compatibility. |
|
12 | 12 | Consult e.g. file 'ipy_profile_sh.py' for an example of the things |
|
13 | 13 | you can do here. |
|
14 | 14 | |
|
15 | 15 | See http://ipython.scipy.org/moin/IpythonExtensionApi for detailed |
|
16 | 16 | description on what you could do here. |
|
17 | 17 | """ |
|
18 | 18 | |
|
19 | 19 | # Most of your config files and extensions will probably start with this import |
|
20 | 20 | |
|
21 | 21 | import IPython.ipapi |
|
22 | 22 | ip = IPython.ipapi.get() |
|
23 | 23 | |
|
24 | 24 | # You probably want to uncomment this if you did %upgrade -nolegacy |
|
25 | 25 | # import ipy_defaults |
|
26 | 26 | |
|
27 | 27 | import os |
|
28 | 28 | |
|
29 | 29 | def main(): |
|
30 | 30 | |
|
31 | 31 | # uncomment if you want to get ipython -p sh behaviour |
|
32 | 32 | # without having to use command line switches |
|
33 | 33 | # import ipy_profile_sh |
|
34 | 34 | |
|
35 | 35 | # Configure your favourite editor? |
|
36 | 36 | # Good idea e.g. for %edit os.path.isfile |
|
37 | 37 | |
|
38 | 38 | #import ipy_editors |
|
39 | 39 | |
|
40 | 40 | # Choose one of these: |
|
41 | 41 | |
|
42 | 42 | #ipy_editors.scite() |
|
43 | 43 | #ipy_editors.scite('c:/opt/scite/scite.exe') |
|
44 | 44 | #ipy_editors.komodo() |
|
45 | 45 | #ipy_editors.idle() |
|
46 | 46 | # ... or many others, try 'ipy_editors??' after import to see them |
|
47 | 47 | |
|
48 | 48 | # Or roll your own: |
|
49 | 49 | #ipy_editors.install_editor("c:/opt/jed +$line $file") |
|
50 | 50 | |
|
51 | 51 | |
|
52 | 52 | o = ip.options |
|
53 | 53 | # An example on how to set options |
|
54 | 54 | #o.autocall = 1 |
|
55 | 55 | o.system_verbose = 0 |
|
56 | 56 | |
|
57 | 57 | #import_all("os sys") |
|
58 | 58 | #execf('~/_ipython/ns.py') |
|
59 | 59 | |
|
60 | 60 | |
|
61 | 61 | # -- prompt |
|
62 | 62 | # A different, more compact set of prompts from the default ones, that |
|
63 | 63 | # always show your current location in the filesystem: |
|
64 | 64 | |
|
65 | 65 | #o.prompt_in1 = r'\C_LightBlue[\C_LightCyan\Y2\C_LightBlue]\C_Normal\n\C_Green|\#>' |
|
66 | 66 | #o.prompt_in2 = r'.\D: ' |
|
67 | 67 | #o.prompt_out = r'[\#] ' |
|
68 | 68 | |
|
69 | 69 | # Try one of these color settings if you can't read the text easily |
|
70 | 70 | # autoexec is a list of IPython commands to execute on startup |
|
71 | 71 | #o.autoexec.append('%colors LightBG') |
|
72 | 72 | #o.autoexec.append('%colors NoColor') |
|
73 | 73 | #o.autoexec.append('%colors Linux') |
|
74 | 74 | |
|
75 | 75 | # for sane integer division that converts to float (1/2 == 0.5) |
|
76 | 76 | #o.autoexec.append('from __future__ import division') |
|
77 | 77 | |
|
78 | 78 | # For %tasks and %kill |
|
79 | 79 | #import jobctrl |
|
80 | 80 | |
|
81 | 81 | # For autoreloading of modules (%autoreload, %aimport) |
|
82 | 82 | #import ipy_autoreload |
|
83 | 83 | |
|
84 | 84 | # For winpdb support (%wdb) |
|
85 | 85 | #import ipy_winpdb |
|
86 | 86 | |
|
87 | 87 | # For bzr completer, requires bzrlib (the python installation of bzr) |
|
88 | 88 | #ip.load('ipy_bzr') |
|
89 | 89 | |
|
90 | 90 | # Tab completer that is not quite so picky (i.e. |
|
91 | 91 | # "foo".<TAB> and str(2).<TAB> will work). Complete |
|
92 | 92 | # at your own risk! |
|
93 | 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 | 106 | # some config helper functions you can use |
|
98 | 107 | def import_all(modules): |
|
99 | 108 | """ Usage: import_all("os sys") """ |
|
100 | 109 | for m in modules.split(): |
|
101 | 110 | ip.ex("from %s import *" % m) |
|
102 | 111 | |
|
103 | 112 | def execf(fname): |
|
104 | 113 | """ Execute a file in user namespace """ |
|
105 | 114 | ip.ex('execfile("%s")' % os.path.expanduser(fname)) |
|
106 | 115 | |
|
107 | 116 | main() |
@@ -1,395 +1,425 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 |
# -*- test-case-name: |
|
|
2 | # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*- | |
|
3 | 3 | |
|
4 |
"""PyObjC classes to provide a Cocoa frontend to the |
|
|
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 | - IPythonCocoaController | |
|
8 | - IPythonCLITextViewDelegate | |
|
7 | To add an IPython interpreter to a cocoa app, instantiate an | |
|
8 | IPythonCocoaController in a XIB and connect its textView outlet to an | |
|
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 | 14 | __docformat__ = "restructuredtext en" |
|
14 | 15 | |
|
15 |
#----------------------------------------------------------------------------- |
|
|
16 | # Copyright (C) 2008 Barry Wark <barrywark@gmail.com> | |
|
16 | #----------------------------------------------------------------------------- | |
|
17 | # Copyright (C) 2008 The IPython Development Team | |
|
17 | 18 | # |
|
18 | 19 | # Distributed under the terms of the BSD License. The full license is in |
|
19 | 20 | # the file COPYING, distributed as part of this software. |
|
20 |
#----------------------------------------------------------------------------- |
|
|
21 | #----------------------------------------------------------------------------- | |
|
21 | 22 | |
|
22 |
#----------------------------------------------------------------------------- |
|
|
23 | #----------------------------------------------------------------------------- | |
|
23 | 24 | # Imports |
|
24 |
#----------------------------------------------------------------------------- |
|
|
25 | #----------------------------------------------------------------------------- | |
|
25 | 26 | |
|
26 | 27 | import objc |
|
27 | 28 | import uuid |
|
28 | 29 | |
|
29 | 30 | from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\ |
|
30 | 31 | NSLog, NSNotificationCenter, NSMakeRange,\ |
|
31 | 32 | NSLocalizedString, NSIntersectionRange |
|
32 | 33 | |
|
33 | 34 | from AppKit import NSApplicationWillTerminateNotification, NSBeep,\ |
|
34 | 35 | NSTextView, NSRulerView, NSVerticalRuler |
|
35 | 36 | |
|
36 | 37 | from pprint import saferepr |
|
37 | 38 | |
|
38 | 39 | import IPython |
|
39 |
from IPython.kernel.engineservice import |
|
|
40 | from IPython.kernel.engineservice import ThreadedEngineService | |
|
40 | 41 | from IPython.frontend.frontendbase import FrontEndBase |
|
41 | 42 | |
|
42 | 43 | from twisted.internet.threads import blockingCallFromThread |
|
43 | 44 | from twisted.python.failure import Failure |
|
44 | 45 | |
|
45 |
#------------------------------------------------------------------------------ |
|
|
46 | #------------------------------------------------------------------------------ | |
|
46 | 47 | # Classes to implement the Cocoa frontend |
|
47 |
#------------------------------------------------------------------------------ |
|
|
48 | #------------------------------------------------------------------------------ | |
|
48 | 49 | |
|
49 | 50 | # TODO: |
|
50 |
# 1. use MultiEngineClient and out-of-process engine rather than |
|
|
51 | # 1. use MultiEngineClient and out-of-process engine rather than | |
|
52 | # ThreadedEngineService? | |
|
51 | 53 | # 2. integrate Xgrid launching of engines |
|
52 | 54 | |
|
53 | 55 | |
|
54 | 56 | |
|
55 | 57 | |
|
56 | 58 | class IPythonCocoaController(NSObject, FrontEndBase): |
|
57 | 59 | userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value)) |
|
58 | 60 | waitingForEngine = objc.ivar().bool() |
|
59 | 61 | textView = objc.IBOutlet() |
|
60 | 62 | |
|
61 | 63 | def init(self): |
|
62 | 64 | self = super(IPythonCocoaController, self).init() |
|
63 | 65 | FrontEndBase.__init__(self, engine=ThreadedEngineService()) |
|
64 | 66 | if(self != None): |
|
65 | 67 | self._common_init() |
|
66 | 68 | |
|
67 | 69 | return self |
|
68 | 70 | |
|
69 | 71 | def _common_init(self): |
|
70 | 72 | """_common_init""" |
|
71 | 73 | |
|
72 | 74 | self.userNS = NSMutableDictionary.dictionary() |
|
73 | 75 | self.waitingForEngine = False |
|
74 | 76 | |
|
75 | 77 | self.lines = {} |
|
76 | 78 | self.tabSpaces = 4 |
|
77 | 79 | self.tabUsesSpaces = True |
|
78 |
self.currentBlockID = self.next |
|
|
80 | self.currentBlockID = self.next_block_ID() | |
|
79 | 81 | self.blockRanges = {} # blockID=>NSRange |
|
80 | 82 | |
|
81 | 83 | |
|
82 | 84 | def awakeFromNib(self): |
|
83 | 85 | """awakeFromNib""" |
|
84 | 86 | |
|
85 | 87 | self._common_init() |
|
86 | 88 | |
|
87 | 89 | # Start the IPython engine |
|
88 | 90 | self.engine.startService() |
|
89 | 91 | NSLog('IPython engine started') |
|
90 | 92 | |
|
91 | 93 | # Register for app termination |
|
92 |
NSNotificationCenter.defaultCenter() |
|
|
93 | 'appWillTerminate:', | |
|
94 |
|
|
|
95 |
|
|
|
94 | nc = NSNotificationCenter.defaultCenter() | |
|
95 | nc.addObserver_selector_name_object_( | |
|
96 | self, | |
|
97 | 'appWillTerminate:', | |
|
98 | NSApplicationWillTerminateNotification, | |
|
99 | None) | |
|
96 | 100 | |
|
97 | 101 | self.textView.setDelegate_(self) |
|
98 | 102 | self.textView.enclosingScrollView().setHasVerticalRuler_(True) |
|
99 |
|
|
|
100 | self.textView.enclosingScrollView(), | |
|
101 | NSVerticalRuler) | |
|
103 | r = NSRulerView.alloc().initWithScrollView_orientation_( | |
|
104 | self.textView.enclosingScrollView(), | |
|
105 | NSVerticalRuler) | |
|
106 | self.verticalRulerView = r | |
|
102 | 107 | self.verticalRulerView.setClientView_(self.textView) |
|
103 |
self. |
|
|
108 | self._start_cli_banner() | |
|
104 | 109 | |
|
105 | 110 | |
|
106 | 111 | def appWillTerminate_(self, notification): |
|
107 | 112 | """appWillTerminate""" |
|
108 | 113 | |
|
109 | 114 | self.engine.stopService() |
|
110 | 115 | |
|
111 | 116 | |
|
112 | 117 | def complete(self, token): |
|
113 | 118 | """Complete token in engine's user_ns |
|
114 | 119 | |
|
115 | 120 | Parameters |
|
116 | 121 | ---------- |
|
117 | 122 | token : string |
|
118 | 123 | |
|
119 | 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 | 130 | return self.engine.complete(token) |
|
125 | 131 | |
|
126 | 132 | |
|
127 | 133 | def execute(self, block, blockID=None): |
|
128 | 134 | self.waitingForEngine = True |
|
129 | 135 | self.willChangeValueForKey_('commandHistory') |
|
130 | 136 | d = super(IPythonCocoaController, self).execute(block, blockID) |
|
131 |
d.addBoth(self._engine |
|
|
132 |
d.addCallback(self._update |
|
|
137 | d.addBoth(self._engine_done) | |
|
138 | d.addCallback(self._update_user_ns) | |
|
133 | 139 | |
|
134 | 140 | return d |
|
135 | 141 | |
|
136 | 142 | |
|
137 |
def _engine |
|
|
143 | def _engine_done(self, x): | |
|
138 | 144 | self.waitingForEngine = False |
|
139 | 145 | self.didChangeValueForKey_('commandHistory') |
|
140 | 146 | return x |
|
141 | 147 | |
|
142 |
def _update |
|
|
148 | def _update_user_ns(self, result): | |
|
143 | 149 | """Update self.userNS from self.engine's namespace""" |
|
144 | 150 | d = self.engine.keys() |
|
145 |
d.addCallback(self._get |
|
|
151 | d.addCallback(self._get_engine_namespace_values_for_keys) | |
|
146 | 152 | |
|
147 | 153 | return result |
|
148 | 154 | |
|
149 | 155 | |
|
150 |
def _get |
|
|
156 | def _get_engine_namespace_values_for_keys(self, keys): | |
|
151 | 157 | d = self.engine.pull(keys) |
|
152 |
d.addCallback(self._store |
|
|
158 | d.addCallback(self._store_engine_namespace_values, keys=keys) | |
|
153 | 159 | |
|
154 | 160 | |
|
155 |
def _store |
|
|
161 | def _store_engine_namespace_values(self, values, keys=[]): | |
|
156 | 162 | assert(len(values) == len(keys)) |
|
157 | 163 | self.willChangeValueForKey_('userNS') |
|
158 | 164 | for (k,v) in zip(keys,values): |
|
159 | 165 | self.userNS[k] = saferepr(v) |
|
160 | 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 | 206 | """Print banner""" |
|
165 | 207 | |
|
166 |
banner = """IPython1 %s -- An enhanced Interactive Python.""" % |
|
|
208 | banner = """IPython1 %s -- An enhanced Interactive Python.""" % \ | |
|
209 | IPython.__version__ | |
|
167 | 210 | |
|
168 | 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 | 308 | def textView_doCommandBySelector_(self, textView, selector): |
|
172 | 309 | assert(textView == self.textView) |
|
173 | 310 | NSLog("textView_doCommandBySelector_: "+selector) |
|
174 | 311 | |
|
175 | 312 | |
|
176 | 313 | if(selector == 'insertNewline:'): |
|
177 |
indent = self.current |
|
|
314 | indent = self.current_indent_string() | |
|
178 | 315 | if(indent): |
|
179 |
line = indent + self.current |
|
|
316 | line = indent + self.current_line() | |
|
180 | 317 | else: |
|
181 |
line = self.current |
|
|
318 | line = self.current_line() | |
|
182 | 319 | |
|
183 |
if(self.is_complete(self.current |
|
|
184 |
self.execute(self.current |
|
|
320 | if(self.is_complete(self.current_block())): | |
|
321 | self.execute(self.current_block(), | |
|
185 | 322 | blockID=self.currentBlockID) |
|
186 |
self.start |
|
|
323 | self.start_new_block() | |
|
187 | 324 | |
|
188 | 325 | return True |
|
189 | 326 | |
|
190 | 327 | return False |
|
191 | 328 | |
|
192 | 329 | elif(selector == 'moveUp:'): |
|
193 |
prevBlock = self.get_history_previous(self.current |
|
|
330 | prevBlock = self.get_history_previous(self.current_block()) | |
|
194 | 331 | if(prevBlock != None): |
|
195 |
self.replace |
|
|
332 | self.replace_current_block_with_string(textView, prevBlock) | |
|
196 | 333 | else: |
|
197 | 334 | NSBeep() |
|
198 | 335 | return True |
|
199 | 336 | |
|
200 | 337 | elif(selector == 'moveDown:'): |
|
201 | 338 | nextBlock = self.get_history_next() |
|
202 | 339 | if(nextBlock != None): |
|
203 |
self.replace |
|
|
340 | self.replace_current_block_with_string(textView, nextBlock) | |
|
204 | 341 | else: |
|
205 | 342 | NSBeep() |
|
206 | 343 | return True |
|
207 | 344 | |
|
208 | 345 | elif(selector == 'moveToBeginningOfParagraph:'): |
|
209 |
textView.setSelectedRange_(NSMakeRange( |
|
|
346 | textView.setSelectedRange_(NSMakeRange( | |
|
347 | self.current_block_range().location, | |
|
348 | 0)) | |
|
210 | 349 | return True |
|
211 | 350 | elif(selector == 'moveToEndOfParagraph:'): |
|
212 |
textView.setSelectedRange_(NSMakeRange( |
|
|
213 |
|
|
|
351 | textView.setSelectedRange_(NSMakeRange( | |
|
352 | self.current_block_range().location + \ | |
|
353 | self.current_block_range().length, 0)) | |
|
214 | 354 | return True |
|
215 | 355 | elif(selector == 'deleteToEndOfParagraph:'): |
|
216 |
if(textView.selectedRange().location <= |
|
|
356 | if(textView.selectedRange().location <= \ | |
|
357 | self.current_block_range().location): | |
|
217 | 358 | # Intersect the selected range with the current line range |
|
218 |
if(self.current |
|
|
359 | if(self.current_block_range().length < 0): | |
|
219 | 360 | self.blockRanges[self.currentBlockID].length = 0 |
|
220 | 361 | |
|
221 | 362 | r = NSIntersectionRange(textView.rangesForUserTextChange()[0], |
|
222 |
self.current |
|
|
363 | self.current_block_range()) | |
|
223 | 364 | |
|
224 | 365 | if(r.length > 0): #no intersection |
|
225 | 366 | textView.setSelectedRange_(r) |
|
226 | 367 | |
|
227 | 368 | return False # don't actually handle the delete |
|
228 | 369 | |
|
229 | 370 | elif(selector == 'insertTab:'): |
|
230 |
if(len(self.current |
|
|
371 | if(len(self.current_line().strip()) == 0): #only white space | |
|
231 | 372 | return False |
|
232 | 373 | else: |
|
233 | 374 | self.textView.complete_(self) |
|
234 | 375 | return True |
|
235 | 376 | |
|
236 | 377 | elif(selector == 'deleteBackward:'): |
|
237 | 378 | #if we're at the beginning of the current block, ignore |
|
238 |
if(textView.selectedRange().location == |
|
|
379 | if(textView.selectedRange().location == \ | |
|
380 | self.current_block_range().location): | |
|
239 | 381 | return True |
|
240 | 382 | else: |
|
241 |
self.current |
|
|
383 | self.current_block_range().length-=1 | |
|
242 | 384 | return False |
|
243 | 385 | return False |
|
244 | 386 | |
|
245 | 387 | |
|
246 |
def textView_shouldChangeTextInRanges_replacementStrings_(self, |
|
|
388 | def textView_shouldChangeTextInRanges_replacementStrings_(self, | |
|
389 | textView, ranges, replacementStrings): | |
|
247 | 390 | """ |
|
248 | 391 | Delegate method for NSTextView. |
|
249 | 392 | |
|
250 |
Refuse change text in ranges not at end, but make those changes at |
|
|
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 | 397 | assert(len(ranges) == len(replacementStrings)) |
|
255 | 398 | allow = True |
|
256 | 399 | for r,s in zip(ranges, replacementStrings): |
|
257 | 400 | r = r.rangeValue() |
|
258 | 401 | if(textView.textStorage().length() > 0 and |
|
259 |
r.location < self.current |
|
|
402 | r.location < self.current_block_range().location): | |
|
260 | 403 | self.insert_text(s) |
|
261 | 404 | allow = False |
|
262 | 405 | |
|
263 | 406 | |
|
264 |
self.blockRanges.setdefault(self.currentBlockID, |
|
|
407 | self.blockRanges.setdefault(self.currentBlockID, | |
|
408 | self.current_block_range()).length +=\ | |
|
409 | len(s) | |
|
265 | 410 | |
|
266 | 411 | return allow |
|
267 | 412 | |
|
268 |
def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, |
|
|
413 | def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, | |
|
414 | textView, words, charRange, index): | |
|
269 | 415 | try: |
|
270 |
t |
|
|
416 | ts = textView.textStorage() | |
|
417 | token = ts.string().substringWithRange_(charRange) | |
|
271 | 418 | completions = blockingCallFromThread(self.complete, token) |
|
272 | 419 | except: |
|
273 | 420 | completions = objc.nil |
|
274 | 421 | NSBeep() |
|
275 | 422 | |
|
276 | 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 | 1 | # encoding: utf-8 |
|
2 |
"""This file contains unittests for the |
|
|
3 | ||
|
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 | |
|
2 | """This file contains unittests for the | |
|
3 | IPython.frontend.cocoa.cocoa_frontend module. | |
|
9 | 4 | """ |
|
10 | 5 | __docformat__ = "restructuredtext en" |
|
11 | ||
|
12 |
#--------------------------------------------------------------------------- |
|
|
13 | # Copyright (C) 2005 Fernando Perez <fperez@colorado.edu> | |
|
14 | # Brian E Granger <ellisonbg@gmail.com> | |
|
15 | # Benjamin Ragan-Kelley <benjaminrk@gmail.com> | |
|
16 | # | |
|
17 | # Distributed under the terms of the BSD License. The full license is in | |
|
18 | # the file COPYING, distributed as part of this software. | |
|
19 |
#--------------------------------------------------------------------------- |
|
|
20 | ||
|
21 |
#--------------------------------------------------------------------------- |
|
|
22 | # Imports | |
|
23 | #------------------------------------------------------------------------------- | |
|
24 | from IPython.kernel.core.interpreter import Interpreter | |
|
6 | ||
|
7 | #--------------------------------------------------------------------------- | |
|
8 | # Copyright (C) 2005 The IPython Development Team | |
|
9 | # | |
|
10 | # Distributed under the terms of the BSD License. The full license is in | |
|
11 | # the file COPYING, distributed as part of this software. | |
|
12 | #--------------------------------------------------------------------------- | |
|
13 | ||
|
14 | #--------------------------------------------------------------------------- | |
|
15 | # Imports | |
|
16 | #--------------------------------------------------------------------------- | |
|
17 | from IPython.kernel.core.interpreter import Interpreter | |
|
25 | 18 | import IPython.kernel.engineservice as es |
|
26 | 19 | from IPython.testing.util import DeferredTestCase |
|
27 | 20 | from twisted.internet.defer import succeed |
|
28 | 21 | from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController |
|
29 | 22 | |
|
30 | 23 | from Foundation import NSMakeRect |
|
31 | 24 | from AppKit import NSTextView, NSScrollView |
|
32 | 25 | |
|
33 | 26 | class TestIPythonCocoaControler(DeferredTestCase): |
|
34 | 27 | """Tests for IPythonCocoaController""" |
|
35 | 28 | |
|
36 | 29 | def setUp(self): |
|
37 | 30 | self.controller = IPythonCocoaController.alloc().init() |
|
38 | 31 | self.engine = es.EngineService() |
|
39 | 32 | self.engine.startService() |
|
40 | 33 | |
|
41 | 34 | |
|
42 | 35 | def tearDown(self): |
|
43 | 36 | self.controller = None |
|
44 | 37 | self.engine.stopService() |
|
45 | 38 | |
|
46 | 39 | def testControllerExecutesCode(self): |
|
47 | 40 | code ="""5+5""" |
|
48 | 41 | expected = Interpreter().execute(code) |
|
49 | 42 | del expected['number'] |
|
50 | 43 | def removeNumberAndID(result): |
|
51 | 44 | del result['number'] |
|
52 | 45 | del result['id'] |
|
53 | 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 | 51 | def testControllerMirrorsUserNSWithValuesAsStrings(self): |
|
57 | 52 | code = """userns1=1;userns2=2""" |
|
58 | 53 | def testControllerUserNS(result): |
|
59 | 54 | self.assertEquals(self.controller.userNS['userns1'], 1) |
|
60 | 55 | self.assertEquals(self.controller.userNS['userns2'], 2) |
|
61 | 56 | |
|
62 | 57 | self.controller.execute(code).addCallback(testControllerUserNS) |
|
63 | 58 | |
|
64 | 59 | |
|
65 | 60 | def testControllerInstantiatesIEngine(self): |
|
66 | 61 | self.assert_(es.IEngineBase.providedBy(self.controller.engine)) |
|
67 | 62 | |
|
68 | 63 | def testControllerCompletesToken(self): |
|
69 | 64 | code = """longNameVariable=10""" |
|
70 | 65 | def testCompletes(result): |
|
71 | 66 | self.assert_("longNameVariable" in result) |
|
72 | 67 | |
|
73 | 68 | def testCompleteToken(result): |
|
74 | 69 | self.controller.complete("longNa").addCallback(testCompletes) |
|
75 | 70 | |
|
76 | 71 | self.controller.execute(code).addCallback(testCompletes) |
|
77 | 72 |
@@ -1,326 +1,352 b'' | |||
|
1 | 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 |
|
|
4 | frontendbase provides an interface and base class for GUI frontends for | |
|
5 | IPython.kernel/IPython.kernel.core. | |
|
4 | 6 | |
|
5 | 7 | Frontend implementations will likely want to subclass FrontEndBase. |
|
6 | 8 | |
|
7 | 9 | Author: Barry Wark |
|
8 | 10 | """ |
|
9 | 11 | __docformat__ = "restructuredtext en" |
|
10 | 12 | |
|
11 | 13 | #------------------------------------------------------------------------------- |
|
12 | 14 | # Copyright (C) 2008 The IPython Development Team |
|
13 | 15 | # |
|
14 | 16 | # Distributed under the terms of the BSD License. The full license is in |
|
15 | 17 | # the file COPYING, distributed as part of this software. |
|
16 | 18 | #------------------------------------------------------------------------------- |
|
17 | 19 | |
|
18 | 20 | #------------------------------------------------------------------------------- |
|
19 | 21 | # Imports |
|
20 | 22 | #------------------------------------------------------------------------------- |
|
21 | 23 | import string |
|
22 | 24 | import uuid |
|
23 | 25 | import _ast |
|
24 | 26 | |
|
25 | 27 | import zope.interface as zi |
|
26 | 28 | |
|
27 | 29 | from IPython.kernel.core.history import FrontEndHistory |
|
28 | 30 | from IPython.kernel.core.util import Bunch |
|
29 | 31 | from IPython.kernel.engineservice import IEngineCore |
|
30 | 32 | |
|
31 | 33 | from twisted.python.failure import Failure |
|
32 | 34 | |
|
33 | 35 | ############################################################################## |
|
34 | 36 | # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or |
|
35 | 37 | # not |
|
36 | 38 | |
|
37 | 39 | rc = Bunch() |
|
38 | 40 | rc.prompt_in1 = r'In [$number]: ' |
|
39 | 41 | rc.prompt_in2 = r'...' |
|
40 | 42 | rc.prompt_out = r'Out [$number]: ' |
|
41 | 43 | |
|
42 | 44 | ############################################################################## |
|
43 | 45 | |
|
44 | 46 | class IFrontEndFactory(zi.Interface): |
|
45 | 47 | """Factory interface for frontends.""" |
|
46 | 48 | |
|
47 | 49 | def __call__(engine=None, history=None): |
|
48 | 50 | """ |
|
49 | 51 | Parameters: |
|
50 | 52 | interpreter : IPython.kernel.engineservice.IEngineCore |
|
51 | 53 | """ |
|
52 | 54 | |
|
53 | 55 | pass |
|
54 | 56 | |
|
55 | 57 | |
|
56 | 58 | |
|
57 | 59 | class IFrontEnd(zi.Interface): |
|
58 | 60 | """Interface for frontends. All methods return t.i.d.Deferred""" |
|
59 | 61 | |
|
60 |
zi.Attribute("input_prompt_template", "string.Template instance |
|
|
61 | zi.Attribute("output_prompt_template", "string.Template instance substituteable with execute result.") | |
|
62 |
zi.Attribute(" |
|
|
62 | zi.Attribute("input_prompt_template", "string.Template instance\ | |
|
63 | 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 | 69 | def update_cell_prompt(self, result): |
|
65 | 70 | """Subclass may override to update the input prompt for a block. |
|
66 |
Since this method will be called as a |
|
|
67 | implementations should return result when finished.""" | |
|
71 | Since this method will be called as a | |
|
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 | 83 | pass |
|
70 | 84 | |
|
71 | 85 | def render_result(self, result): |
|
72 |
"""Render the result of an execute call. Implementors may choose the |
|
|
73 | For example, a notebook-style frontend might render a Chaco plot inline. | |
|
86 | """Render the result of an execute call. Implementors may choose the | |
|
87 | method of rendering. | |
|
88 | For example, a notebook-style frontend might render a Chaco plot | |
|
89 | inline. | |
|
74 | 90 | |
|
75 | 91 | Parameters: |
|
76 | 92 | result : dict (result of IEngineBase.execute ) |
|
77 | 93 | |
|
78 | 94 | Result: |
|
79 | 95 | Output of frontend rendering |
|
80 | 96 | """ |
|
81 | 97 | |
|
82 | 98 | pass |
|
83 | 99 | |
|
84 | 100 | def render_error(self, failure): |
|
85 |
"""Subclasses must override to render the failure. Since this method |
|
|
86 |
twisted.internet.defer.Deferred's callback, |
|
|
87 | when finished.""" | |
|
101 | """Subclasses must override to render the failure. Since this method | |
|
102 | ill be called as a twisted.internet.defer.Deferred's callback, | |
|
103 | implementations should return result when finished. | |
|
104 | """ | |
|
88 | 105 | |
|
89 | 106 | pass |
|
90 | 107 | |
|
91 | 108 | |
|
92 | 109 | def input_prompt(result={}): |
|
93 |
"""Returns the input prompt by subsituting into |
|
|
110 | """Returns the input prompt by subsituting into | |
|
111 | self.input_prompt_template | |
|
112 | """ | |
|
94 | 113 | pass |
|
95 | 114 | |
|
96 | 115 | def output_prompt(result): |
|
97 |
"""Returns the output prompt by subsituting into |
|
|
116 | """Returns the output prompt by subsituting into | |
|
117 | self.output_prompt_template | |
|
118 | """ | |
|
98 | 119 | |
|
99 | 120 | pass |
|
100 | 121 | |
|
101 | 122 | def continuation_prompt(): |
|
102 |
"""Returns the continuation prompt by subsituting into |
|
|
123 | """Returns the continuation prompt by subsituting into | |
|
124 | self.continuation_prompt_template | |
|
125 | """ | |
|
103 | 126 | |
|
104 | 127 | pass |
|
105 | 128 | |
|
106 | 129 | def is_complete(block): |
|
107 | 130 | """Returns True if block is complete, False otherwise.""" |
|
108 | 131 | |
|
109 | 132 | pass |
|
110 | 133 | |
|
111 | 134 | def compile_ast(block): |
|
112 | 135 | """Compiles block to an _ast.AST""" |
|
113 | 136 | |
|
114 | 137 | pass |
|
115 | 138 | |
|
116 | 139 | |
|
117 | 140 | def get_history_previous(currentBlock): |
|
118 | 141 | """Returns the block previous in the history. Saves currentBlock if |
|
119 | 142 | the history_cursor is currently at the end of the input history""" |
|
120 | 143 | pass |
|
121 | 144 | |
|
122 | 145 | def get_history_next(): |
|
123 | 146 | """Returns the next block in the history.""" |
|
124 | 147 | |
|
125 | 148 | pass |
|
126 | 149 | |
|
127 | 150 | |
|
128 | 151 | class FrontEndBase(object): |
|
129 | 152 | """ |
|
130 | 153 | FrontEndBase manages the state tasks for a CLI frontend: |
|
131 | 154 | - Input and output history management |
|
132 | 155 | - Input/continuation and output prompt generation |
|
133 | 156 | |
|
134 | 157 | Some issues (due to possibly unavailable engine): |
|
135 | 158 | - How do we get the current cell number for the engine? |
|
136 | 159 | - How do we handle completions? |
|
137 | 160 | """ |
|
138 | 161 | |
|
139 | 162 | zi.implements(IFrontEnd) |
|
140 | 163 | zi.classProvides(IFrontEndFactory) |
|
141 | 164 | |
|
142 | 165 | history_cursor = 0 |
|
143 | 166 | |
|
144 | 167 | current_indent_level = 0 |
|
145 | 168 | |
|
146 | 169 | |
|
147 | 170 | input_prompt_template = string.Template(rc.prompt_in1) |
|
148 | 171 | output_prompt_template = string.Template(rc.prompt_out) |
|
149 | 172 | continuation_prompt_template = string.Template(rc.prompt_in2) |
|
150 | 173 | |
|
151 | 174 | def __init__(self, engine=None, history=None): |
|
152 | 175 | assert(engine==None or IEngineCore.providedBy(engine)) |
|
153 | 176 | self.engine = IEngineCore(engine) |
|
154 | 177 | if history is None: |
|
155 | 178 | self.history = FrontEndHistory(input_cache=['']) |
|
156 | 179 | else: |
|
157 | 180 | self.history = history |
|
158 | 181 | |
|
159 | 182 | |
|
160 | 183 | def input_prompt(self, result={}): |
|
161 | 184 | """Returns the current input prompt |
|
162 | 185 | |
|
163 | 186 | It would be great to use ipython1.core.prompts.Prompt1 here |
|
164 | 187 | """ |
|
165 | 188 | |
|
166 | 189 | result.setdefault('number','') |
|
167 | 190 | |
|
168 | 191 | return self.input_prompt_template.safe_substitute(result) |
|
169 | 192 | |
|
170 | 193 | |
|
171 | 194 | def continuation_prompt(self): |
|
172 | 195 | """Returns the current continuation prompt""" |
|
173 | 196 | |
|
174 | 197 | return self.continuation_prompt_template.safe_substitute() |
|
175 | 198 | |
|
176 | 199 | def output_prompt(self, result): |
|
177 | 200 | """Returns the output prompt for result""" |
|
178 | 201 | |
|
179 | 202 | return self.output_prompt_template.safe_substitute(result) |
|
180 | 203 | |
|
181 | 204 | |
|
182 | 205 | def is_complete(self, block): |
|
183 | 206 | """Determine if block is complete. |
|
184 | 207 | |
|
185 | 208 | Parameters |
|
186 | 209 | block : string |
|
187 | 210 | |
|
188 | 211 | Result |
|
189 | 212 | True if block can be sent to the engine without compile errors. |
|
190 | 213 | False otherwise. |
|
191 | 214 | """ |
|
192 | 215 | |
|
193 | 216 | try: |
|
194 | 217 | ast = self.compile_ast(block) |
|
195 | 218 | except: |
|
196 | 219 | return False |
|
197 | 220 | |
|
198 | 221 | lines = block.split('\n') |
|
199 | 222 | return (len(lines)==1 or str(lines[-1])=='') |
|
200 | 223 | |
|
201 | 224 | |
|
202 | 225 | def compile_ast(self, block): |
|
203 | 226 | """Compile block to an AST |
|
204 | 227 | |
|
205 | 228 | Parameters: |
|
206 | 229 | block : str |
|
207 | 230 | |
|
208 | 231 | Result: |
|
209 | 232 | AST |
|
210 | 233 | |
|
211 | 234 | Throws: |
|
212 | 235 | Exception if block cannot be compiled |
|
213 | 236 | """ |
|
214 | 237 | |
|
215 | 238 | return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST) |
|
216 | 239 | |
|
217 | 240 | |
|
218 | 241 | def execute(self, block, blockID=None): |
|
219 | 242 | """Execute the block and return result. |
|
220 | 243 | |
|
221 | 244 | Parameters: |
|
222 | 245 | block : {str, AST} |
|
223 | 246 | blockID : any |
|
224 |
Caller may provide an ID to identify this block. |
|
|
247 | Caller may provide an ID to identify this block. | |
|
248 | result['blockID'] := blockID | |
|
225 | 249 | |
|
226 | 250 | Result: |
|
227 | 251 | Deferred result of self.interpreter.execute |
|
228 | 252 | """ |
|
229 | 253 | |
|
230 | 254 | if(not self.is_complete(block)): |
|
231 | 255 | return Failure(Exception("Block is not compilable")) |
|
232 | 256 | |
|
233 | 257 | if(blockID == None): |
|
234 | 258 | blockID = uuid.uuid4() #random UUID |
|
235 | 259 | |
|
236 | 260 | d = self.engine.execute(block) |
|
237 | 261 | d.addCallback(self._add_history, block=block) |
|
238 | 262 | d.addBoth(self._add_block_id, blockID) |
|
239 | 263 | d.addBoth(self.update_cell_prompt) |
|
240 | 264 | d.addCallbacks(self.render_result, errback=self.render_error) |
|
241 | 265 | |
|
242 | 266 | return d |
|
243 | 267 | |
|
244 | 268 | |
|
245 | 269 | def _add_block_id(self, result, blockID): |
|
246 |
"""Add the blockID to result or failure. Unfortunatley, we have to |
|
|
247 | differently than result dicts | |
|
270 | """Add the blockID to result or failure. Unfortunatley, we have to | |
|
271 | treat failures differently than result dicts. | |
|
248 | 272 | """ |
|
249 | 273 | |
|
250 | 274 | if(isinstance(result, Failure)): |
|
251 | 275 | result.blockID = blockID |
|
252 | 276 | else: |
|
253 | 277 | result['blockID'] = blockID |
|
254 | 278 | |
|
255 | 279 | return result |
|
256 | 280 | |
|
257 | 281 | def _add_history(self, result, block=None): |
|
258 | 282 | """Add block to the history""" |
|
259 | 283 | |
|
260 | 284 | assert(block != None) |
|
261 | 285 | self.history.add_items([block]) |
|
262 | 286 | self.history_cursor += 1 |
|
263 | 287 | |
|
264 | 288 | return result |
|
265 | 289 | |
|
266 | 290 | |
|
267 | 291 | def get_history_previous(self, currentBlock): |
|
268 | 292 | """ Returns previous history string and decrement history cursor. |
|
269 | 293 | """ |
|
270 | 294 | command = self.history.get_history_item(self.history_cursor - 1) |
|
271 | 295 | |
|
272 | 296 | if command is not None: |
|
273 | 297 | if(self.history_cursor == len(self.history.input_cache)): |
|
274 | 298 | self.history.input_cache[self.history_cursor] = currentBlock |
|
275 | 299 | self.history_cursor -= 1 |
|
276 | 300 | return command |
|
277 | 301 | |
|
278 | 302 | |
|
279 | 303 | def get_history_next(self): |
|
280 | 304 | """ Returns next history string and increment history cursor. |
|
281 | 305 | """ |
|
282 | 306 | command = self.history.get_history_item(self.history_cursor+1) |
|
283 | 307 | |
|
284 | 308 | if command is not None: |
|
285 | 309 | self.history_cursor += 1 |
|
286 | 310 | return command |
|
287 | 311 | |
|
288 | 312 | ### |
|
289 | 313 | # Subclasses probably want to override these methods... |
|
290 | 314 | ### |
|
291 | 315 | |
|
292 | 316 | def update_cell_prompt(self, result): |
|
293 | 317 | """Subclass may override to update the input prompt for a block. |
|
294 |
Since this method will be called as a |
|
|
295 | implementations should return result when finished. | |
|
318 | Since this method will be called as a | |
|
319 | twisted.internet.defer.Deferred's callback, implementations should | |
|
320 | return result when finished. | |
|
296 | 321 | |
|
297 |
N |
|
|
298 | do something like:: | |
|
322 | NB: result is a failure if the execute returned a failre. | |
|
323 | To get the blockID, you should do something like:: | |
|
299 | 324 | if(isinstance(result, twisted.python.failure.Failure)): |
|
300 | 325 | blockID = result.blockID |
|
301 | 326 | else: |
|
302 | 327 | blockID = result['blockID'] |
|
303 | 328 | |
|
304 | 329 | |
|
305 | 330 | """ |
|
306 | 331 | |
|
307 | 332 | return result |
|
308 | 333 | |
|
309 | 334 | |
|
310 | 335 | def render_result(self, result): |
|
311 |
"""Subclasses must override to render result. Since this method will |
|
|
312 |
twisted.internet.defer.Deferred's callback, |
|
|
313 | when finished.""" | |
|
336 | """Subclasses must override to render result. Since this method will | |
|
337 | be called as a twisted.internet.defer.Deferred's callback, | |
|
338 | implementations should return result when finished. | |
|
339 | """ | |
|
314 | 340 | |
|
315 | 341 | return result |
|
316 | 342 | |
|
317 | 343 | |
|
318 | 344 | def render_error(self, failure): |
|
319 |
"""Subclasses must override to render the failure. Since this method |
|
|
320 |
twisted.internet.defer.Deferred's callback, |
|
|
321 | when finished.""" | |
|
345 | """Subclasses must override to render the failure. Since this method | |
|
346 | will be called as a twisted.internet.defer.Deferred's callback, | |
|
347 | implementations should return result when finished.""" | |
|
322 | 348 | |
|
323 | 349 | return failure |
|
324 | 350 | |
|
325 | 351 | |
|
326 | 352 |
@@ -1,149 +1,151 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | |
|
3 | 3 | """This file contains unittests for the frontendbase module.""" |
|
4 | 4 | |
|
5 | 5 | __docformat__ = "restructuredtext en" |
|
6 | 6 | |
|
7 |
#--------------------------------------------------------------------------- |
|
|
8 | # Copyright (C) 2008 The IPython Development Team | |
|
9 | # | |
|
10 | # Distributed under the terms of the BSD License. The full license is in | |
|
11 | # the file COPYING, distributed as part of this software. | |
|
12 |
#--------------------------------------------------------------------------- |
|
|
13 | ||
|
14 |
#--------------------------------------------------------------------------- |
|
|
15 | # Imports | |
|
16 |
#--------------------------------------------------------------------------- |
|
|
7 | #--------------------------------------------------------------------------- | |
|
8 | # Copyright (C) 2008 The IPython Development Team | |
|
9 | # | |
|
10 | # Distributed under the terms of the BSD License. The full license is in | |
|
11 | # the file COPYING, distributed as part of this software. | |
|
12 | #--------------------------------------------------------------------------- | |
|
13 | ||
|
14 | #--------------------------------------------------------------------------- | |
|
15 | # Imports | |
|
16 | #--------------------------------------------------------------------------- | |
|
17 | 17 | |
|
18 | 18 | import unittest |
|
19 | 19 | from IPython.frontend import frontendbase |
|
20 | 20 | from IPython.kernel.engineservice import EngineService |
|
21 | 21 | |
|
22 | 22 | class FrontEndCallbackChecker(frontendbase.FrontEndBase): |
|
23 | 23 | """FrontEndBase subclass for checking callbacks""" |
|
24 | 24 | def __init__(self, engine=None, history=None): |
|
25 |
super(FrontEndCallbackChecker, self).__init__(engine=engine, |
|
|
25 | super(FrontEndCallbackChecker, self).__init__(engine=engine, | |
|
26 | history=history) | |
|
26 | 27 | self.updateCalled = False |
|
27 | 28 | self.renderResultCalled = False |
|
28 | 29 | self.renderErrorCalled = False |
|
29 | 30 | |
|
30 | 31 | def update_cell_prompt(self, result): |
|
31 | 32 | self.updateCalled = True |
|
32 | 33 | return result |
|
33 | 34 | |
|
34 | 35 | def render_result(self, result): |
|
35 | 36 | self.renderResultCalled = True |
|
36 | 37 | return result |
|
37 | 38 | |
|
38 | 39 | |
|
39 | 40 | def render_error(self, failure): |
|
40 | 41 | self.renderErrorCalled = True |
|
41 | 42 | return failure |
|
42 | 43 | |
|
43 | 44 | |
|
44 | 45 | |
|
45 | 46 | |
|
46 | 47 | class TestFrontendBase(unittest.TestCase): |
|
47 | 48 | def setUp(self): |
|
48 | 49 | """Setup the EngineService and FrontEndBase""" |
|
49 | 50 | |
|
50 | 51 | self.fb = FrontEndCallbackChecker(engine=EngineService()) |
|
51 | 52 | |
|
52 | 53 | |
|
53 | 54 | def test_implements_IFrontEnd(self): |
|
54 |
assert(frontendbase.IFrontEnd.implementedBy( |
|
|
55 | assert(frontendbase.IFrontEnd.implementedBy( | |
|
56 | frontendbase.FrontEndBase)) | |
|
55 | 57 | |
|
56 | 58 | |
|
57 | 59 | def test_is_complete_returns_False_for_incomplete_block(self): |
|
58 | 60 | """""" |
|
59 | 61 | |
|
60 | 62 | block = """def test(a):""" |
|
61 | 63 | |
|
62 | 64 | assert(self.fb.is_complete(block) == False) |
|
63 | 65 | |
|
64 | 66 | def test_is_complete_returns_True_for_complete_block(self): |
|
65 | 67 | """""" |
|
66 | 68 | |
|
67 | 69 | block = """def test(a): pass""" |
|
68 | 70 | |
|
69 | 71 | assert(self.fb.is_complete(block)) |
|
70 | 72 | |
|
71 | 73 | block = """a=3""" |
|
72 | 74 | |
|
73 | 75 | assert(self.fb.is_complete(block)) |
|
74 | 76 | |
|
75 | 77 | |
|
76 | 78 | def test_blockID_added_to_result(self): |
|
77 | 79 | block = """3+3""" |
|
78 | 80 | |
|
79 | 81 | d = self.fb.execute(block, blockID='TEST_ID') |
|
80 | 82 | |
|
81 | 83 | d.addCallback(self.checkBlockID, expected='TEST_ID') |
|
82 | 84 | |
|
83 | 85 | def test_blockID_added_to_failure(self): |
|
84 | 86 | block = "raise Exception()" |
|
85 | 87 | |
|
86 | 88 | d = self.fb.execute(block,blockID='TEST_ID') |
|
87 | 89 | d.addErrback(self.checkFailureID, expected='TEST_ID') |
|
88 | 90 | |
|
89 | 91 | def checkBlockID(self, result, expected=""): |
|
90 | 92 | assert(result['blockID'] == expected) |
|
91 | 93 | |
|
92 | 94 | |
|
93 | 95 | def checkFailureID(self, failure, expected=""): |
|
94 | 96 | assert(failure.blockID == expected) |
|
95 | 97 | |
|
96 | 98 | |
|
97 | 99 | def test_callbacks_added_to_execute(self): |
|
98 | 100 | """test that |
|
99 | 101 | update_cell_prompt |
|
100 | 102 | render_result |
|
101 | 103 | |
|
102 | 104 | are added to execute request |
|
103 | 105 | """ |
|
104 | 106 | |
|
105 | 107 | d = self.fb.execute("10+10") |
|
106 | 108 | d.addCallback(self.checkCallbacks) |
|
107 | 109 | |
|
108 | 110 | |
|
109 | 111 | def checkCallbacks(self, result): |
|
110 | 112 | assert(self.fb.updateCalled) |
|
111 | 113 | assert(self.fb.renderResultCalled) |
|
112 | 114 | |
|
113 | 115 | |
|
114 | 116 | def test_error_callback_added_to_execute(self): |
|
115 | 117 | """test that render_error called on execution error""" |
|
116 | 118 | |
|
117 | 119 | d = self.fb.execute("raise Exception()") |
|
118 | 120 | d.addCallback(self.checkRenderError) |
|
119 | 121 | |
|
120 | 122 | def checkRenderError(self, result): |
|
121 | 123 | assert(self.fb.renderErrorCalled) |
|
122 | 124 | |
|
123 | 125 | def test_history_returns_expected_block(self): |
|
124 | 126 | """Make sure history browsing doesn't fail""" |
|
125 | 127 | |
|
126 | 128 | blocks = ["a=1","a=2","a=3"] |
|
127 | 129 | for b in blocks: |
|
128 | 130 | d = self.fb.execute(b) |
|
129 | 131 | |
|
130 | 132 | # d is now the deferred for the last executed block |
|
131 | 133 | d.addCallback(self.historyTests, blocks) |
|
132 | 134 | |
|
133 | 135 | |
|
134 | 136 | def historyTests(self, result, blocks): |
|
135 | 137 | """historyTests""" |
|
136 | 138 | |
|
137 | 139 | assert(len(blocks) >= 3) |
|
138 | 140 | assert(self.fb.get_history_previous("") == blocks[-2]) |
|
139 | 141 | assert(self.fb.get_history_previous("") == blocks[-3]) |
|
140 | 142 | assert(self.fb.get_history_next() == blocks[-2]) |
|
141 | 143 | |
|
142 | 144 | |
|
143 | 145 | def test_history_returns_none_at_startup(self): |
|
144 | 146 | """test_history_returns_none_at_startup""" |
|
145 | 147 | |
|
146 | 148 | assert(self.fb.get_history_previous("")==None) |
|
147 | 149 | assert(self.fb.get_history_next()==None) |
|
148 | 150 | |
|
149 | 151 |
@@ -1,2046 +1,2097 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """ |
|
3 | 3 | General purpose utilities. |
|
4 | 4 | |
|
5 | 5 | This is a grab-bag of stuff I find useful in most programs I write. Some of |
|
6 | 6 | these things are also convenient when working at the command line. |
|
7 | 7 | |
|
8 | 8 | $Id: genutils.py 2998 2008-01-31 10:06:04Z vivainio $""" |
|
9 | 9 | |
|
10 | 10 | #***************************************************************************** |
|
11 | 11 | # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu> |
|
12 | 12 | # |
|
13 | 13 | # Distributed under the terms of the BSD License. The full license is in |
|
14 | 14 | # the file COPYING, distributed as part of this software. |
|
15 | 15 | #***************************************************************************** |
|
16 | 16 | |
|
17 | 17 | from IPython import Release |
|
18 | 18 | __author__ = '%s <%s>' % Release.authors['Fernando'] |
|
19 | 19 | __license__ = Release.license |
|
20 | 20 | |
|
21 | 21 | #**************************************************************************** |
|
22 | 22 | # required modules from the Python standard library |
|
23 | 23 | import __main__ |
|
24 | 24 | import commands |
|
25 | 25 | try: |
|
26 | 26 | import doctest |
|
27 | 27 | except ImportError: |
|
28 | 28 | pass |
|
29 | 29 | import os |
|
30 | import platform | |
|
30 | 31 | import re |
|
31 | 32 | import shlex |
|
32 | 33 | import shutil |
|
34 | import subprocess | |
|
33 | 35 | import sys |
|
34 | 36 | import tempfile |
|
35 | 37 | import time |
|
36 | 38 | import types |
|
37 | 39 | import warnings |
|
38 | 40 | |
|
39 | 41 | # Curses and termios are Unix-only modules |
|
40 | 42 | try: |
|
41 | 43 | import curses |
|
42 | 44 | # We need termios as well, so if its import happens to raise, we bail on |
|
43 | 45 | # using curses altogether. |
|
44 | 46 | import termios |
|
45 | 47 | except ImportError: |
|
46 | 48 | USE_CURSES = False |
|
47 | 49 | else: |
|
48 | 50 | # Curses on Solaris may not be complete, so we can't use it there |
|
49 | 51 | USE_CURSES = hasattr(curses,'initscr') |
|
50 | 52 | |
|
51 | 53 | # Other IPython utilities |
|
52 | 54 | import IPython |
|
53 | 55 | from IPython.Itpl import Itpl,itpl,printpl |
|
54 | 56 | from IPython import DPyGetOpt, platutils |
|
55 | 57 | from IPython.generics import result_display |
|
56 | 58 | import IPython.ipapi |
|
57 | 59 | from IPython.external.path import path |
|
58 | 60 | if os.name == "nt": |
|
59 | 61 | from IPython.winconsole import get_console_size |
|
60 | 62 | |
|
61 | 63 | try: |
|
62 | 64 | set |
|
63 | 65 | except: |
|
64 | 66 | from sets import Set as set |
|
65 | 67 | |
|
66 | 68 | |
|
67 | 69 | #**************************************************************************** |
|
68 | 70 | # Exceptions |
|
69 | 71 | class Error(Exception): |
|
70 | 72 | """Base class for exceptions in this module.""" |
|
71 | 73 | pass |
|
72 | 74 | |
|
73 | 75 | #---------------------------------------------------------------------------- |
|
74 | 76 | class IOStream: |
|
75 | 77 | def __init__(self,stream,fallback): |
|
76 | 78 | if not hasattr(stream,'write') or not hasattr(stream,'flush'): |
|
77 | 79 | stream = fallback |
|
78 | 80 | self.stream = stream |
|
79 | 81 | self._swrite = stream.write |
|
80 | 82 | self.flush = stream.flush |
|
81 | 83 | |
|
82 | 84 | def write(self,data): |
|
83 | 85 | try: |
|
84 | 86 | self._swrite(data) |
|
85 | 87 | except: |
|
86 | 88 | try: |
|
87 | 89 | # print handles some unicode issues which may trip a plain |
|
88 | 90 | # write() call. Attempt to emulate write() by using a |
|
89 | 91 | # trailing comma |
|
90 | 92 | print >> self.stream, data, |
|
91 | 93 | except: |
|
92 | 94 | # if we get here, something is seriously broken. |
|
93 | 95 | print >> sys.stderr, \ |
|
94 | 96 | 'ERROR - failed to write data to stream:', self.stream |
|
95 | 97 | |
|
96 | 98 | def close(self): |
|
97 | 99 | pass |
|
98 | 100 | |
|
99 | 101 | |
|
100 | 102 | class IOTerm: |
|
101 | 103 | """ Term holds the file or file-like objects for handling I/O operations. |
|
102 | 104 | |
|
103 | 105 | These are normally just sys.stdin, sys.stdout and sys.stderr but for |
|
104 | 106 | Windows they can can replaced to allow editing the strings before they are |
|
105 | 107 | displayed.""" |
|
106 | 108 | |
|
107 | 109 | # In the future, having IPython channel all its I/O operations through |
|
108 | 110 | # this class will make it easier to embed it into other environments which |
|
109 | 111 | # are not a normal terminal (such as a GUI-based shell) |
|
110 | 112 | def __init__(self,cin=None,cout=None,cerr=None): |
|
111 | 113 | self.cin = IOStream(cin,sys.stdin) |
|
112 | 114 | self.cout = IOStream(cout,sys.stdout) |
|
113 | 115 | self.cerr = IOStream(cerr,sys.stderr) |
|
114 | 116 | |
|
115 | 117 | # Global variable to be used for all I/O |
|
116 | 118 | Term = IOTerm() |
|
117 | 119 | |
|
118 | 120 | import IPython.rlineimpl as readline |
|
119 | 121 | # Remake Term to use the readline i/o facilities |
|
120 | 122 | if sys.platform == 'win32' and readline.have_readline: |
|
121 | 123 | |
|
122 | 124 | Term = IOTerm(cout=readline._outputfile,cerr=readline._outputfile) |
|
123 | 125 | |
|
124 | 126 | |
|
125 | 127 | #**************************************************************************** |
|
126 | 128 | # Generic warning/error printer, used by everything else |
|
127 | 129 | def warn(msg,level=2,exit_val=1): |
|
128 | 130 | """Standard warning printer. Gives formatting consistency. |
|
129 | 131 | |
|
130 | 132 | Output is sent to Term.cerr (sys.stderr by default). |
|
131 | 133 | |
|
132 | 134 | Options: |
|
133 | 135 | |
|
134 | 136 | -level(2): allows finer control: |
|
135 | 137 | 0 -> Do nothing, dummy function. |
|
136 | 138 | 1 -> Print message. |
|
137 | 139 | 2 -> Print 'WARNING:' + message. (Default level). |
|
138 | 140 | 3 -> Print 'ERROR:' + message. |
|
139 | 141 | 4 -> Print 'FATAL ERROR:' + message and trigger a sys.exit(exit_val). |
|
140 | 142 | |
|
141 | 143 | -exit_val (1): exit value returned by sys.exit() for a level 4 |
|
142 | 144 | warning. Ignored for all other levels.""" |
|
143 | 145 | |
|
144 | 146 | if level>0: |
|
145 | 147 | header = ['','','WARNING: ','ERROR: ','FATAL ERROR: '] |
|
146 | 148 | print >> Term.cerr, '%s%s' % (header[level],msg) |
|
147 | 149 | if level == 4: |
|
148 | 150 | print >> Term.cerr,'Exiting.\n' |
|
149 | 151 | sys.exit(exit_val) |
|
150 | 152 | |
|
151 | 153 | def info(msg): |
|
152 | 154 | """Equivalent to warn(msg,level=1).""" |
|
153 | 155 | |
|
154 | 156 | warn(msg,level=1) |
|
155 | 157 | |
|
156 | 158 | def error(msg): |
|
157 | 159 | """Equivalent to warn(msg,level=3).""" |
|
158 | 160 | |
|
159 | 161 | warn(msg,level=3) |
|
160 | 162 | |
|
161 | 163 | def fatal(msg,exit_val=1): |
|
162 | 164 | """Equivalent to warn(msg,exit_val=exit_val,level=4).""" |
|
163 | 165 | |
|
164 | 166 | warn(msg,exit_val=exit_val,level=4) |
|
165 | 167 | |
|
166 | 168 | #--------------------------------------------------------------------------- |
|
167 | 169 | # Debugging routines |
|
168 | 170 | # |
|
169 | 171 | def debugx(expr,pre_msg=''): |
|
170 | 172 | """Print the value of an expression from the caller's frame. |
|
171 | 173 | |
|
172 | 174 | Takes an expression, evaluates it in the caller's frame and prints both |
|
173 | 175 | the given expression and the resulting value (as well as a debug mark |
|
174 | 176 | indicating the name of the calling function. The input must be of a form |
|
175 | 177 | suitable for eval(). |
|
176 | 178 | |
|
177 | 179 | An optional message can be passed, which will be prepended to the printed |
|
178 | 180 | expr->value pair.""" |
|
179 | 181 | |
|
180 | 182 | cf = sys._getframe(1) |
|
181 | 183 | print '[DBG:%s] %s%s -> %r' % (cf.f_code.co_name,pre_msg,expr, |
|
182 | 184 | eval(expr,cf.f_globals,cf.f_locals)) |
|
183 | 185 | |
|
184 | 186 | # deactivate it by uncommenting the following line, which makes it a no-op |
|
185 | 187 | #def debugx(expr,pre_msg=''): pass |
|
186 | 188 | |
|
187 | 189 | #---------------------------------------------------------------------------- |
|
188 | 190 | StringTypes = types.StringTypes |
|
189 | 191 | |
|
190 | 192 | # Basic timing functionality |
|
191 | 193 | |
|
192 | 194 | # If possible (Unix), use the resource module instead of time.clock() |
|
193 | 195 | try: |
|
194 | 196 | import resource |
|
195 | 197 | def clocku(): |
|
196 | 198 | """clocku() -> floating point number |
|
197 | 199 | |
|
198 | 200 | Return the *USER* CPU time in seconds since the start of the process. |
|
199 | 201 | This is done via a call to resource.getrusage, so it avoids the |
|
200 | 202 | wraparound problems in time.clock().""" |
|
201 | 203 | |
|
202 | 204 | return resource.getrusage(resource.RUSAGE_SELF)[0] |
|
203 | 205 | |
|
204 | 206 | def clocks(): |
|
205 | 207 | """clocks() -> floating point number |
|
206 | 208 | |
|
207 | 209 | Return the *SYSTEM* CPU time in seconds since the start of the process. |
|
208 | 210 | This is done via a call to resource.getrusage, so it avoids the |
|
209 | 211 | wraparound problems in time.clock().""" |
|
210 | 212 | |
|
211 | 213 | return resource.getrusage(resource.RUSAGE_SELF)[1] |
|
212 | 214 | |
|
213 | 215 | def clock(): |
|
214 | 216 | """clock() -> floating point number |
|
215 | 217 | |
|
216 | 218 | Return the *TOTAL USER+SYSTEM* CPU time in seconds since the start of |
|
217 | 219 | the process. This is done via a call to resource.getrusage, so it |
|
218 | 220 | avoids the wraparound problems in time.clock().""" |
|
219 | 221 | |
|
220 | 222 | u,s = resource.getrusage(resource.RUSAGE_SELF)[:2] |
|
221 | 223 | return u+s |
|
222 | 224 | |
|
223 | 225 | def clock2(): |
|
224 | 226 | """clock2() -> (t_user,t_system) |
|
225 | 227 | |
|
226 | 228 | Similar to clock(), but return a tuple of user/system times.""" |
|
227 | 229 | return resource.getrusage(resource.RUSAGE_SELF)[:2] |
|
228 | 230 | |
|
229 | 231 | except ImportError: |
|
230 | 232 | # There is no distinction of user/system time under windows, so we just use |
|
231 | 233 | # time.clock() for everything... |
|
232 | 234 | clocku = clocks = clock = time.clock |
|
233 | 235 | def clock2(): |
|
234 | 236 | """Under windows, system CPU time can't be measured. |
|
235 | 237 | |
|
236 | 238 | This just returns clock() and zero.""" |
|
237 | 239 | return time.clock(),0.0 |
|
238 | 240 | |
|
239 | 241 | def timings_out(reps,func,*args,**kw): |
|
240 | 242 | """timings_out(reps,func,*args,**kw) -> (t_total,t_per_call,output) |
|
241 | 243 | |
|
242 | 244 | Execute a function reps times, return a tuple with the elapsed total |
|
243 | 245 | CPU time in seconds, the time per call and the function's output. |
|
244 | 246 | |
|
245 | 247 | Under Unix, the return value is the sum of user+system time consumed by |
|
246 | 248 | the process, computed via the resource module. This prevents problems |
|
247 | 249 | related to the wraparound effect which the time.clock() function has. |
|
248 | 250 | |
|
249 | 251 | Under Windows the return value is in wall clock seconds. See the |
|
250 | 252 | documentation for the time module for more details.""" |
|
251 | 253 | |
|
252 | 254 | reps = int(reps) |
|
253 | 255 | assert reps >=1, 'reps must be >= 1' |
|
254 | 256 | if reps==1: |
|
255 | 257 | start = clock() |
|
256 | 258 | out = func(*args,**kw) |
|
257 | 259 | tot_time = clock()-start |
|
258 | 260 | else: |
|
259 | 261 | rng = xrange(reps-1) # the last time is executed separately to store output |
|
260 | 262 | start = clock() |
|
261 | 263 | for dummy in rng: func(*args,**kw) |
|
262 | 264 | out = func(*args,**kw) # one last time |
|
263 | 265 | tot_time = clock()-start |
|
264 | 266 | av_time = tot_time / reps |
|
265 | 267 | return tot_time,av_time,out |
|
266 | 268 | |
|
267 | 269 | def timings(reps,func,*args,**kw): |
|
268 | 270 | """timings(reps,func,*args,**kw) -> (t_total,t_per_call) |
|
269 | 271 | |
|
270 | 272 | Execute a function reps times, return a tuple with the elapsed total CPU |
|
271 | 273 | time in seconds and the time per call. These are just the first two values |
|
272 | 274 | in timings_out().""" |
|
273 | 275 | |
|
274 | 276 | return timings_out(reps,func,*args,**kw)[0:2] |
|
275 | 277 | |
|
276 | 278 | def timing(func,*args,**kw): |
|
277 | 279 | """timing(func,*args,**kw) -> t_total |
|
278 | 280 | |
|
279 | 281 | Execute a function once, return the elapsed total CPU time in |
|
280 | 282 | seconds. This is just the first value in timings_out().""" |
|
281 | 283 | |
|
282 | 284 | return timings_out(1,func,*args,**kw)[0] |
|
283 | 285 | |
|
284 | 286 | #**************************************************************************** |
|
285 | 287 | # file and system |
|
286 | 288 | |
|
287 | 289 | def arg_split(s,posix=False): |
|
288 | 290 | """Split a command line's arguments in a shell-like manner. |
|
289 | 291 | |
|
290 | 292 | This is a modified version of the standard library's shlex.split() |
|
291 | 293 | function, but with a default of posix=False for splitting, so that quotes |
|
292 | 294 | in inputs are respected.""" |
|
293 | 295 | |
|
294 | 296 | # XXX - there may be unicode-related problems here!!! I'm not sure that |
|
295 | 297 | # shlex is truly unicode-safe, so it might be necessary to do |
|
296 | 298 | # |
|
297 | 299 | # s = s.encode(sys.stdin.encoding) |
|
298 | 300 | # |
|
299 | 301 | # first, to ensure that shlex gets a normal string. Input from anyone who |
|
300 | 302 | # knows more about unicode and shlex than I would be good to have here... |
|
301 | 303 | lex = shlex.shlex(s, posix=posix) |
|
302 | 304 | lex.whitespace_split = True |
|
303 | 305 | return list(lex) |
|
304 | 306 | |
|
305 | 307 | def system(cmd,verbose=0,debug=0,header=''): |
|
306 | 308 | """Execute a system command, return its exit status. |
|
307 | 309 | |
|
308 | 310 | Options: |
|
309 | 311 | |
|
310 | 312 | - verbose (0): print the command to be executed. |
|
311 | 313 | |
|
312 | 314 | - debug (0): only print, do not actually execute. |
|
313 | 315 | |
|
314 | 316 | - header (''): Header to print on screen prior to the executed command (it |
|
315 | 317 | is only prepended to the command, no newlines are added). |
|
316 | 318 | |
|
317 | 319 | Note: a stateful version of this function is available through the |
|
318 | 320 | SystemExec class.""" |
|
319 | 321 | |
|
320 | 322 | stat = 0 |
|
321 | 323 | if verbose or debug: print header+cmd |
|
322 | 324 | sys.stdout.flush() |
|
323 | 325 | if not debug: stat = os.system(cmd) |
|
324 | 326 | return stat |
|
325 | 327 | |
|
326 | 328 | def abbrev_cwd(): |
|
327 | 329 | """ Return abbreviated version of cwd, e.g. d:mydir """ |
|
328 | 330 | cwd = os.getcwd().replace('\\','/') |
|
329 | 331 | drivepart = '' |
|
330 | 332 | tail = cwd |
|
331 | 333 | if sys.platform == 'win32': |
|
332 | 334 | if len(cwd) < 4: |
|
333 | 335 | return cwd |
|
334 | 336 | drivepart,tail = os.path.splitdrive(cwd) |
|
335 | 337 | |
|
336 | 338 | |
|
337 | 339 | parts = tail.split('/') |
|
338 | 340 | if len(parts) > 2: |
|
339 | 341 | tail = '/'.join(parts[-2:]) |
|
340 | 342 | |
|
341 | 343 | return (drivepart + ( |
|
342 | 344 | cwd == '/' and '/' or tail)) |
|
343 | 345 | |
|
344 | 346 | |
|
345 | 347 | # This function is used by ipython in a lot of places to make system calls. |
|
346 | 348 | # We need it to be slightly different under win32, due to the vagaries of |
|
347 | 349 | # 'network shares'. A win32 override is below. |
|
348 | 350 | |
|
349 | 351 | def shell(cmd,verbose=0,debug=0,header=''): |
|
350 | 352 | """Execute a command in the system shell, always return None. |
|
351 | 353 | |
|
352 | 354 | Options: |
|
353 | 355 | |
|
354 | 356 | - verbose (0): print the command to be executed. |
|
355 | 357 | |
|
356 | 358 | - debug (0): only print, do not actually execute. |
|
357 | 359 | |
|
358 | 360 | - header (''): Header to print on screen prior to the executed command (it |
|
359 | 361 | is only prepended to the command, no newlines are added). |
|
360 | 362 | |
|
361 | 363 | Note: this is similar to genutils.system(), but it returns None so it can |
|
362 | 364 | be conveniently used in interactive loops without getting the return value |
|
363 | 365 | (typically 0) printed many times.""" |
|
364 | 366 | |
|
365 | 367 | stat = 0 |
|
366 | 368 | if verbose or debug: print header+cmd |
|
367 | 369 | # flush stdout so we don't mangle python's buffering |
|
368 | 370 | sys.stdout.flush() |
|
369 | 371 | |
|
370 | 372 | if not debug: |
|
371 | 373 | platutils.set_term_title("IPy " + cmd) |
|
372 | 374 | os.system(cmd) |
|
373 | 375 | platutils.set_term_title("IPy " + abbrev_cwd()) |
|
374 | 376 | |
|
375 | 377 | # override shell() for win32 to deal with network shares |
|
376 | 378 | if os.name in ('nt','dos'): |
|
377 | 379 | |
|
378 | 380 | shell_ori = shell |
|
379 | 381 | |
|
380 | 382 | def shell(cmd,verbose=0,debug=0,header=''): |
|
381 | 383 | if os.getcwd().startswith(r"\\"): |
|
382 | 384 | path = os.getcwd() |
|
383 | 385 | # change to c drive (cannot be on UNC-share when issuing os.system, |
|
384 | 386 | # as cmd.exe cannot handle UNC addresses) |
|
385 | 387 | os.chdir("c:") |
|
386 | 388 | # issue pushd to the UNC-share and then run the command |
|
387 | 389 | try: |
|
388 | 390 | shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header) |
|
389 | 391 | finally: |
|
390 | 392 | os.chdir(path) |
|
391 | 393 | else: |
|
392 | 394 | shell_ori(cmd,verbose,debug,header) |
|
393 | 395 | |
|
394 | 396 | shell.__doc__ = shell_ori.__doc__ |
|
395 | 397 | |
|
396 | 398 | def getoutput(cmd,verbose=0,debug=0,header='',split=0): |
|
397 | 399 | """Dummy substitute for perl's backquotes. |
|
398 | 400 | |
|
399 | 401 | Executes a command and returns the output. |
|
400 | 402 | |
|
401 | 403 | Accepts the same arguments as system(), plus: |
|
402 | 404 | |
|
403 | 405 | - split(0): if true, the output is returned as a list split on newlines. |
|
404 | 406 | |
|
405 | 407 | Note: a stateful version of this function is available through the |
|
406 | 408 | SystemExec class. |
|
407 | 409 | |
|
408 | 410 | This is pretty much deprecated and rarely used, |
|
409 | 411 | genutils.getoutputerror may be what you need. |
|
410 | 412 | |
|
411 | 413 | """ |
|
412 | 414 | |
|
413 | 415 | if verbose or debug: print header+cmd |
|
414 | 416 | if not debug: |
|
415 | 417 | output = os.popen(cmd).read() |
|
416 | 418 | # stipping last \n is here for backwards compat. |
|
417 | 419 | if output.endswith('\n'): |
|
418 | 420 | output = output[:-1] |
|
419 | 421 | if split: |
|
420 | 422 | return output.split('\n') |
|
421 | 423 | else: |
|
422 | 424 | return output |
|
423 | 425 | |
|
424 | 426 | def getoutputerror(cmd,verbose=0,debug=0,header='',split=0): |
|
425 | 427 | """Return (standard output,standard error) of executing cmd in a shell. |
|
426 | 428 | |
|
427 | 429 | Accepts the same arguments as system(), plus: |
|
428 | 430 | |
|
429 | 431 | - split(0): if true, each of stdout/err is returned as a list split on |
|
430 | 432 | newlines. |
|
431 | 433 | |
|
432 | 434 | Note: a stateful version of this function is available through the |
|
433 | 435 | SystemExec class.""" |
|
434 | 436 | |
|
435 | 437 | if verbose or debug: print header+cmd |
|
436 | 438 | if not cmd: |
|
437 | 439 | if split: |
|
438 | 440 | return [],[] |
|
439 | 441 | else: |
|
440 | 442 | return '','' |
|
441 | 443 | if not debug: |
|
442 | 444 | pin,pout,perr = os.popen3(cmd) |
|
443 | 445 | tout = pout.read().rstrip() |
|
444 | 446 | terr = perr.read().rstrip() |
|
445 | 447 | pin.close() |
|
446 | 448 | pout.close() |
|
447 | 449 | perr.close() |
|
448 | 450 | if split: |
|
449 | 451 | return tout.split('\n'),terr.split('\n') |
|
450 | 452 | else: |
|
451 | 453 | return tout,terr |
|
452 | 454 | |
|
453 | 455 | # for compatibility with older naming conventions |
|
454 | 456 | xsys = system |
|
455 | 457 | bq = getoutput |
|
456 | 458 | |
|
457 | 459 | class SystemExec: |
|
458 | 460 | """Access the system and getoutput functions through a stateful interface. |
|
459 | 461 | |
|
460 | 462 | Note: here we refer to the system and getoutput functions from this |
|
461 | 463 | library, not the ones from the standard python library. |
|
462 | 464 | |
|
463 | 465 | This class offers the system and getoutput functions as methods, but the |
|
464 | 466 | verbose, debug and header parameters can be set for the instance (at |
|
465 | 467 | creation time or later) so that they don't need to be specified on each |
|
466 | 468 | call. |
|
467 | 469 | |
|
468 | 470 | For efficiency reasons, there's no way to override the parameters on a |
|
469 | 471 | per-call basis other than by setting instance attributes. If you need |
|
470 | 472 | local overrides, it's best to directly call system() or getoutput(). |
|
471 | 473 | |
|
472 | 474 | The following names are provided as alternate options: |
|
473 | 475 | - xsys: alias to system |
|
474 | 476 | - bq: alias to getoutput |
|
475 | 477 | |
|
476 | 478 | An instance can then be created as: |
|
477 | 479 | >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ') |
|
478 | 480 | |
|
479 | 481 | And used as: |
|
480 | 482 | >>> sysexec.xsys('pwd') |
|
481 | 483 | >>> dirlist = sysexec.bq('ls -l') |
|
482 | 484 | """ |
|
483 | 485 | |
|
484 | 486 | def __init__(self,verbose=0,debug=0,header='',split=0): |
|
485 | 487 | """Specify the instance's values for verbose, debug and header.""" |
|
486 | 488 | setattr_list(self,'verbose debug header split') |
|
487 | 489 | |
|
488 | 490 | def system(self,cmd): |
|
489 | 491 | """Stateful interface to system(), with the same keyword parameters.""" |
|
490 | 492 | |
|
491 | 493 | system(cmd,self.verbose,self.debug,self.header) |
|
492 | 494 | |
|
493 | 495 | def shell(self,cmd): |
|
494 | 496 | """Stateful interface to shell(), with the same keyword parameters.""" |
|
495 | 497 | |
|
496 | 498 | shell(cmd,self.verbose,self.debug,self.header) |
|
497 | 499 | |
|
498 | 500 | xsys = system # alias |
|
499 | 501 | |
|
500 | 502 | def getoutput(self,cmd): |
|
501 | 503 | """Stateful interface to getoutput().""" |
|
502 | 504 | |
|
503 | 505 | return getoutput(cmd,self.verbose,self.debug,self.header,self.split) |
|
504 | 506 | |
|
505 | 507 | def getoutputerror(self,cmd): |
|
506 | 508 | """Stateful interface to getoutputerror().""" |
|
507 | 509 | |
|
508 | 510 | return getoutputerror(cmd,self.verbose,self.debug,self.header,self.split) |
|
509 | 511 | |
|
510 | 512 | bq = getoutput # alias |
|
511 | 513 | |
|
512 | 514 | #----------------------------------------------------------------------------- |
|
513 | 515 | def mutex_opts(dict,ex_op): |
|
514 | 516 | """Check for presence of mutually exclusive keys in a dict. |
|
515 | 517 | |
|
516 | 518 | Call: mutex_opts(dict,[[op1a,op1b],[op2a,op2b]...]""" |
|
517 | 519 | for op1,op2 in ex_op: |
|
518 | 520 | if op1 in dict and op2 in dict: |
|
519 | 521 | raise ValueError,'\n*** ERROR in Arguments *** '\ |
|
520 | 522 | 'Options '+op1+' and '+op2+' are mutually exclusive.' |
|
521 | 523 | |
|
522 | 524 | #----------------------------------------------------------------------------- |
|
523 | 525 | def get_py_filename(name): |
|
524 | 526 | """Return a valid python filename in the current directory. |
|
525 | 527 | |
|
526 | 528 | If the given name is not a file, it adds '.py' and searches again. |
|
527 | 529 | Raises IOError with an informative message if the file isn't found.""" |
|
528 | 530 | |
|
529 | 531 | name = os.path.expanduser(name) |
|
530 | 532 | if not os.path.isfile(name) and not name.endswith('.py'): |
|
531 | 533 | name += '.py' |
|
532 | 534 | if os.path.isfile(name): |
|
533 | 535 | return name |
|
534 | 536 | else: |
|
535 | 537 | raise IOError,'File `%s` not found.' % name |
|
536 | 538 | |
|
537 | 539 | #----------------------------------------------------------------------------- |
|
538 | 540 | def filefind(fname,alt_dirs = None): |
|
539 | 541 | """Return the given filename either in the current directory, if it |
|
540 | 542 | exists, or in a specified list of directories. |
|
541 | 543 | |
|
542 | 544 | ~ expansion is done on all file and directory names. |
|
543 | 545 | |
|
544 | 546 | Upon an unsuccessful search, raise an IOError exception.""" |
|
545 | 547 | |
|
546 | 548 | if alt_dirs is None: |
|
547 | 549 | try: |
|
548 | 550 | alt_dirs = get_home_dir() |
|
549 | 551 | except HomeDirError: |
|
550 | 552 | alt_dirs = os.getcwd() |
|
551 | 553 | search = [fname] + list_strings(alt_dirs) |
|
552 | 554 | search = map(os.path.expanduser,search) |
|
553 | 555 | #print 'search list for',fname,'list:',search # dbg |
|
554 | 556 | fname = search[0] |
|
555 | 557 | if os.path.isfile(fname): |
|
556 | 558 | return fname |
|
557 | 559 | for direc in search[1:]: |
|
558 | 560 | testname = os.path.join(direc,fname) |
|
559 | 561 | #print 'testname',testname # dbg |
|
560 | 562 | if os.path.isfile(testname): |
|
561 | 563 | return testname |
|
562 | 564 | raise IOError,'File' + `fname` + \ |
|
563 | 565 | ' not found in current or supplied directories:' + `alt_dirs` |
|
564 | 566 | |
|
565 | 567 | #---------------------------------------------------------------------------- |
|
566 | 568 | def file_read(filename): |
|
567 | 569 | """Read a file and close it. Returns the file source.""" |
|
568 | 570 | fobj = open(filename,'r'); |
|
569 | 571 | source = fobj.read(); |
|
570 | 572 | fobj.close() |
|
571 | 573 | return source |
|
572 | 574 | |
|
573 | 575 | def file_readlines(filename): |
|
574 | 576 | """Read a file and close it. Returns the file source using readlines().""" |
|
575 | 577 | fobj = open(filename,'r'); |
|
576 | 578 | lines = fobj.readlines(); |
|
577 | 579 | fobj.close() |
|
578 | 580 | return lines |
|
579 | 581 | |
|
580 | 582 | #---------------------------------------------------------------------------- |
|
581 | 583 | def target_outdated(target,deps): |
|
582 | 584 | """Determine whether a target is out of date. |
|
583 | 585 | |
|
584 | 586 | target_outdated(target,deps) -> 1/0 |
|
585 | 587 | |
|
586 | 588 | deps: list of filenames which MUST exist. |
|
587 | 589 | target: single filename which may or may not exist. |
|
588 | 590 | |
|
589 | 591 | If target doesn't exist or is older than any file listed in deps, return |
|
590 | 592 | true, otherwise return false. |
|
591 | 593 | """ |
|
592 | 594 | try: |
|
593 | 595 | target_time = os.path.getmtime(target) |
|
594 | 596 | except os.error: |
|
595 | 597 | return 1 |
|
596 | 598 | for dep in deps: |
|
597 | 599 | dep_time = os.path.getmtime(dep) |
|
598 | 600 | if dep_time > target_time: |
|
599 | 601 | #print "For target",target,"Dep failed:",dep # dbg |
|
600 | 602 | #print "times (dep,tar):",dep_time,target_time # dbg |
|
601 | 603 | return 1 |
|
602 | 604 | return 0 |
|
603 | 605 | |
|
604 | 606 | #----------------------------------------------------------------------------- |
|
605 | 607 | def target_update(target,deps,cmd): |
|
606 | 608 | """Update a target with a given command given a list of dependencies. |
|
607 | 609 | |
|
608 | 610 | target_update(target,deps,cmd) -> runs cmd if target is outdated. |
|
609 | 611 | |
|
610 | 612 | This is just a wrapper around target_outdated() which calls the given |
|
611 | 613 | command if target is outdated.""" |
|
612 | 614 | |
|
613 | 615 | if target_outdated(target,deps): |
|
614 | 616 | xsys(cmd) |
|
615 | 617 | |
|
616 | 618 | #---------------------------------------------------------------------------- |
|
617 | 619 | def unquote_ends(istr): |
|
618 | 620 | """Remove a single pair of quotes from the endpoints of a string.""" |
|
619 | 621 | |
|
620 | 622 | if not istr: |
|
621 | 623 | return istr |
|
622 | 624 | if (istr[0]=="'" and istr[-1]=="'") or \ |
|
623 | 625 | (istr[0]=='"' and istr[-1]=='"'): |
|
624 | 626 | return istr[1:-1] |
|
625 | 627 | else: |
|
626 | 628 | return istr |
|
627 | 629 | |
|
628 | 630 | #---------------------------------------------------------------------------- |
|
629 | 631 | def process_cmdline(argv,names=[],defaults={},usage=''): |
|
630 | 632 | """ Process command-line options and arguments. |
|
631 | 633 | |
|
632 | 634 | Arguments: |
|
633 | 635 | |
|
634 | 636 | - argv: list of arguments, typically sys.argv. |
|
635 | 637 | |
|
636 | 638 | - names: list of option names. See DPyGetOpt docs for details on options |
|
637 | 639 | syntax. |
|
638 | 640 | |
|
639 | 641 | - defaults: dict of default values. |
|
640 | 642 | |
|
641 | 643 | - usage: optional usage notice to print if a wrong argument is passed. |
|
642 | 644 | |
|
643 | 645 | Return a dict of options and a list of free arguments.""" |
|
644 | 646 | |
|
645 | 647 | getopt = DPyGetOpt.DPyGetOpt() |
|
646 | 648 | getopt.setIgnoreCase(0) |
|
647 | 649 | getopt.parseConfiguration(names) |
|
648 | 650 | |
|
649 | 651 | try: |
|
650 | 652 | getopt.processArguments(argv) |
|
651 | 653 | except DPyGetOpt.ArgumentError, exc: |
|
652 | 654 | print usage |
|
653 | 655 | warn('"%s"' % exc,level=4) |
|
654 | 656 | |
|
655 | 657 | defaults.update(getopt.optionValues) |
|
656 | 658 | args = getopt.freeValues |
|
657 | 659 | |
|
658 | 660 | return defaults,args |
|
659 | 661 | |
|
660 | 662 | #---------------------------------------------------------------------------- |
|
661 | 663 | def optstr2types(ostr): |
|
662 | 664 | """Convert a string of option names to a dict of type mappings. |
|
663 | 665 | |
|
664 | 666 | optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'} |
|
665 | 667 | |
|
666 | 668 | This is used to get the types of all the options in a string formatted |
|
667 | 669 | with the conventions of DPyGetOpt. The 'type' None is used for options |
|
668 | 670 | which are strings (they need no further conversion). This function's main |
|
669 | 671 | use is to get a typemap for use with read_dict(). |
|
670 | 672 | """ |
|
671 | 673 | |
|
672 | 674 | typeconv = {None:'',int:'',float:''} |
|
673 | 675 | typemap = {'s':None,'i':int,'f':float} |
|
674 | 676 | opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)') |
|
675 | 677 | |
|
676 | 678 | for w in ostr.split(): |
|
677 | 679 | oname,alias,otype = opt_re.match(w).groups() |
|
678 | 680 | if otype == '' or alias == '!': # simple switches are integers too |
|
679 | 681 | otype = 'i' |
|
680 | 682 | typeconv[typemap[otype]] += oname + ' ' |
|
681 | 683 | return typeconv |
|
682 | 684 | |
|
683 | 685 | #---------------------------------------------------------------------------- |
|
684 | 686 | def read_dict(filename,type_conv=None,**opt): |
|
685 | 687 | |
|
686 | 688 | """Read a dictionary of key=value pairs from an input file, optionally |
|
687 | 689 | performing conversions on the resulting values. |
|
688 | 690 | |
|
689 | 691 | read_dict(filename,type_conv,**opt) -> dict |
|
690 | 692 | |
|
691 | 693 | Only one value per line is accepted, the format should be |
|
692 | 694 | # optional comments are ignored |
|
693 | 695 | key value\n |
|
694 | 696 | |
|
695 | 697 | Args: |
|
696 | 698 | |
|
697 | 699 | - type_conv: A dictionary specifying which keys need to be converted to |
|
698 | 700 | which types. By default all keys are read as strings. This dictionary |
|
699 | 701 | should have as its keys valid conversion functions for strings |
|
700 | 702 | (int,long,float,complex, or your own). The value for each key |
|
701 | 703 | (converter) should be a whitespace separated string containing the names |
|
702 | 704 | of all the entries in the file to be converted using that function. For |
|
703 | 705 | keys to be left alone, use None as the conversion function (only needed |
|
704 | 706 | with purge=1, see below). |
|
705 | 707 | |
|
706 | 708 | - opt: dictionary with extra options as below (default in parens) |
|
707 | 709 | |
|
708 | 710 | purge(0): if set to 1, all keys *not* listed in type_conv are purged out |
|
709 | 711 | of the dictionary to be returned. If purge is going to be used, the |
|
710 | 712 | set of keys to be left as strings also has to be explicitly specified |
|
711 | 713 | using the (non-existent) conversion function None. |
|
712 | 714 | |
|
713 | 715 | fs(None): field separator. This is the key/value separator to be used |
|
714 | 716 | when parsing the file. The None default means any whitespace [behavior |
|
715 | 717 | of string.split()]. |
|
716 | 718 | |
|
717 | 719 | strip(0): if 1, strip string values of leading/trailinig whitespace. |
|
718 | 720 | |
|
719 | 721 | warn(1): warning level if requested keys are not found in file. |
|
720 | 722 | - 0: silently ignore. |
|
721 | 723 | - 1: inform but proceed. |
|
722 | 724 | - 2: raise KeyError exception. |
|
723 | 725 | |
|
724 | 726 | no_empty(0): if 1, remove keys with whitespace strings as a value. |
|
725 | 727 | |
|
726 | 728 | unique([]): list of keys (or space separated string) which can't be |
|
727 | 729 | repeated. If one such key is found in the file, each new instance |
|
728 | 730 | overwrites the previous one. For keys not listed here, the behavior is |
|
729 | 731 | to make a list of all appearances. |
|
730 | 732 | |
|
731 | 733 | Example: |
|
732 | 734 | If the input file test.ini has: |
|
733 | 735 | i 3 |
|
734 | 736 | x 4.5 |
|
735 | 737 | y 5.5 |
|
736 | 738 | s hi ho |
|
737 | 739 | Then: |
|
738 | 740 | |
|
739 | 741 | >>> type_conv={int:'i',float:'x',None:'s'} |
|
740 | 742 | >>> read_dict('test.ini') |
|
741 | 743 | {'i': '3', 's': 'hi ho', 'x': '4.5', 'y': '5.5'} |
|
742 | 744 | >>> read_dict('test.ini',type_conv) |
|
743 | 745 | {'i': 3, 's': 'hi ho', 'x': 4.5, 'y': '5.5'} |
|
744 | 746 | >>> read_dict('test.ini',type_conv,purge=1) |
|
745 | 747 | {'i': 3, 's': 'hi ho', 'x': 4.5} |
|
746 | 748 | """ |
|
747 | 749 | |
|
748 | 750 | # starting config |
|
749 | 751 | opt.setdefault('purge',0) |
|
750 | 752 | opt.setdefault('fs',None) # field sep defaults to any whitespace |
|
751 | 753 | opt.setdefault('strip',0) |
|
752 | 754 | opt.setdefault('warn',1) |
|
753 | 755 | opt.setdefault('no_empty',0) |
|
754 | 756 | opt.setdefault('unique','') |
|
755 | 757 | if type(opt['unique']) in StringTypes: |
|
756 | 758 | unique_keys = qw(opt['unique']) |
|
757 | 759 | elif type(opt['unique']) in (types.TupleType,types.ListType): |
|
758 | 760 | unique_keys = opt['unique'] |
|
759 | 761 | else: |
|
760 | 762 | raise ValueError, 'Unique keys must be given as a string, List or Tuple' |
|
761 | 763 | |
|
762 | 764 | dict = {} |
|
763 | 765 | # first read in table of values as strings |
|
764 | 766 | file = open(filename,'r') |
|
765 | 767 | for line in file.readlines(): |
|
766 | 768 | line = line.strip() |
|
767 | 769 | if len(line) and line[0]=='#': continue |
|
768 | 770 | if len(line)>0: |
|
769 | 771 | lsplit = line.split(opt['fs'],1) |
|
770 | 772 | try: |
|
771 | 773 | key,val = lsplit |
|
772 | 774 | except ValueError: |
|
773 | 775 | key,val = lsplit[0],'' |
|
774 | 776 | key = key.strip() |
|
775 | 777 | if opt['strip']: val = val.strip() |
|
776 | 778 | if val == "''" or val == '""': val = '' |
|
777 | 779 | if opt['no_empty'] and (val=='' or val.isspace()): |
|
778 | 780 | continue |
|
779 | 781 | # if a key is found more than once in the file, build a list |
|
780 | 782 | # unless it's in the 'unique' list. In that case, last found in file |
|
781 | 783 | # takes precedence. User beware. |
|
782 | 784 | try: |
|
783 | 785 | if dict[key] and key in unique_keys: |
|
784 | 786 | dict[key] = val |
|
785 | 787 | elif type(dict[key]) is types.ListType: |
|
786 | 788 | dict[key].append(val) |
|
787 | 789 | else: |
|
788 | 790 | dict[key] = [dict[key],val] |
|
789 | 791 | except KeyError: |
|
790 | 792 | dict[key] = val |
|
791 | 793 | # purge if requested |
|
792 | 794 | if opt['purge']: |
|
793 | 795 | accepted_keys = qwflat(type_conv.values()) |
|
794 | 796 | for key in dict.keys(): |
|
795 | 797 | if key in accepted_keys: continue |
|
796 | 798 | del(dict[key]) |
|
797 | 799 | # now convert if requested |
|
798 | 800 | if type_conv==None: return dict |
|
799 | 801 | conversions = type_conv.keys() |
|
800 | 802 | try: conversions.remove(None) |
|
801 | 803 | except: pass |
|
802 | 804 | for convert in conversions: |
|
803 | 805 | for val in qw(type_conv[convert]): |
|
804 | 806 | try: |
|
805 | 807 | dict[val] = convert(dict[val]) |
|
806 | 808 | except KeyError,e: |
|
807 | 809 | if opt['warn'] == 0: |
|
808 | 810 | pass |
|
809 | 811 | elif opt['warn'] == 1: |
|
810 | 812 | print >>sys.stderr, 'Warning: key',val,\ |
|
811 | 813 | 'not found in file',filename |
|
812 | 814 | elif opt['warn'] == 2: |
|
813 | 815 | raise KeyError,e |
|
814 | 816 | else: |
|
815 | 817 | raise ValueError,'Warning level must be 0,1 or 2' |
|
816 | 818 | |
|
817 | 819 | return dict |
|
818 | 820 | |
|
819 | 821 | #---------------------------------------------------------------------------- |
|
820 | 822 | def flag_calls(func): |
|
821 | 823 | """Wrap a function to detect and flag when it gets called. |
|
822 | 824 | |
|
823 | 825 | This is a decorator which takes a function and wraps it in a function with |
|
824 | 826 | a 'called' attribute. wrapper.called is initialized to False. |
|
825 | 827 | |
|
826 | 828 | The wrapper.called attribute is set to False right before each call to the |
|
827 | 829 | wrapped function, so if the call fails it remains False. After the call |
|
828 | 830 | completes, wrapper.called is set to True and the output is returned. |
|
829 | 831 | |
|
830 | 832 | Testing for truth in wrapper.called allows you to determine if a call to |
|
831 | 833 | func() was attempted and succeeded.""" |
|
832 | 834 | |
|
833 | 835 | def wrapper(*args,**kw): |
|
834 | 836 | wrapper.called = False |
|
835 | 837 | out = func(*args,**kw) |
|
836 | 838 | wrapper.called = True |
|
837 | 839 | return out |
|
838 | 840 | |
|
839 | 841 | wrapper.called = False |
|
840 | 842 | wrapper.__doc__ = func.__doc__ |
|
841 | 843 | return wrapper |
|
842 | 844 | |
|
843 | 845 | #---------------------------------------------------------------------------- |
|
844 | 846 | def dhook_wrap(func,*a,**k): |
|
845 | 847 | """Wrap a function call in a sys.displayhook controller. |
|
846 | 848 | |
|
847 | 849 | Returns a wrapper around func which calls func, with all its arguments and |
|
848 | 850 | keywords unmodified, using the default sys.displayhook. Since IPython |
|
849 | 851 | modifies sys.displayhook, it breaks the behavior of certain systems that |
|
850 | 852 | rely on the default behavior, notably doctest. |
|
851 | 853 | """ |
|
852 | 854 | |
|
853 | 855 | def f(*a,**k): |
|
854 | 856 | |
|
855 | 857 | dhook_s = sys.displayhook |
|
856 | 858 | sys.displayhook = sys.__displayhook__ |
|
857 | 859 | try: |
|
858 | 860 | out = func(*a,**k) |
|
859 | 861 | finally: |
|
860 | 862 | sys.displayhook = dhook_s |
|
861 | 863 | |
|
862 | 864 | return out |
|
863 | 865 | |
|
864 | 866 | f.__doc__ = func.__doc__ |
|
865 | 867 | return f |
|
866 | 868 | |
|
867 | 869 | #---------------------------------------------------------------------------- |
|
868 | 870 | def doctest_reload(): |
|
869 | 871 | """Properly reload doctest to reuse it interactively. |
|
870 | 872 | |
|
871 | 873 | This routine: |
|
872 | 874 | |
|
873 | 875 | - reloads doctest |
|
874 | 876 | |
|
875 | 877 | - resets its global 'master' attribute to None, so that multiple uses of |
|
876 | 878 | the module interactively don't produce cumulative reports. |
|
877 | 879 | |
|
878 | 880 | - Monkeypatches its core test runner method to protect it from IPython's |
|
879 | 881 | modified displayhook. Doctest expects the default displayhook behavior |
|
880 | 882 | deep down, so our modification breaks it completely. For this reason, a |
|
881 | 883 | hard monkeypatch seems like a reasonable solution rather than asking |
|
882 | 884 | users to manually use a different doctest runner when under IPython.""" |
|
883 | 885 | |
|
884 | 886 | import doctest |
|
885 | 887 | reload(doctest) |
|
886 | 888 | doctest.master=None |
|
887 | 889 | |
|
888 | 890 | try: |
|
889 | 891 | doctest.DocTestRunner |
|
890 | 892 | except AttributeError: |
|
891 | 893 | # This is only for python 2.3 compatibility, remove once we move to |
|
892 | 894 | # 2.4 only. |
|
893 | 895 | pass |
|
894 | 896 | else: |
|
895 | 897 | doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run) |
|
896 | 898 | |
|
897 | 899 | #---------------------------------------------------------------------------- |
|
898 | 900 | class HomeDirError(Error): |
|
899 | 901 | pass |
|
900 | 902 | |
|
901 | 903 | def get_home_dir(): |
|
902 | 904 | """Return the closest possible equivalent to a 'home' directory. |
|
903 | 905 | |
|
904 | 906 | We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH. |
|
905 | 907 | |
|
906 | 908 | Currently only Posix and NT are implemented, a HomeDirError exception is |
|
907 | 909 | raised for all other OSes. """ |
|
908 | 910 | |
|
909 | 911 | isdir = os.path.isdir |
|
910 | 912 | env = os.environ |
|
911 | 913 | |
|
912 | 914 | # first, check py2exe distribution root directory for _ipython. |
|
913 | 915 | # This overrides all. Normally does not exist. |
|
914 | 916 | |
|
915 | 917 | if '\\library.zip\\' in IPython.__file__.lower(): |
|
916 | 918 | root, rest = IPython.__file__.lower().split('library.zip') |
|
917 | 919 | if isdir(root + '_ipython'): |
|
918 | 920 | os.environ["IPYKITROOT"] = root.rstrip('\\') |
|
919 | 921 | return root |
|
920 | 922 | |
|
921 | 923 | try: |
|
922 | 924 | homedir = env['HOME'] |
|
923 | 925 | if not isdir(homedir): |
|
924 | 926 | # in case a user stuck some string which does NOT resolve to a |
|
925 | 927 | # valid path, it's as good as if we hadn't foud it |
|
926 | 928 | raise KeyError |
|
927 | 929 | return homedir |
|
928 | 930 | except KeyError: |
|
929 | 931 | if os.name == 'posix': |
|
930 | 932 | raise HomeDirError,'undefined $HOME, IPython can not proceed.' |
|
931 | 933 | elif os.name == 'nt': |
|
932 | 934 | # For some strange reason, win9x returns 'nt' for os.name. |
|
933 | 935 | try: |
|
934 | 936 | homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH']) |
|
935 | 937 | if not isdir(homedir): |
|
936 | 938 | homedir = os.path.join(env['USERPROFILE']) |
|
937 | 939 | if not isdir(homedir): |
|
938 | 940 | raise HomeDirError |
|
939 | 941 | return homedir |
|
940 | 942 | except: |
|
941 | 943 | try: |
|
942 | 944 | # Use the registry to get the 'My Documents' folder. |
|
943 | 945 | import _winreg as wreg |
|
944 | 946 | key = wreg.OpenKey(wreg.HKEY_CURRENT_USER, |
|
945 | 947 | "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") |
|
946 | 948 | homedir = wreg.QueryValueEx(key,'Personal')[0] |
|
947 | 949 | key.Close() |
|
948 | 950 | if not isdir(homedir): |
|
949 | 951 | e = ('Invalid "Personal" folder registry key ' |
|
950 | 952 | 'typically "My Documents".\n' |
|
951 | 953 | 'Value: %s\n' |
|
952 | 954 | 'This is not a valid directory on your system.' % |
|
953 | 955 | homedir) |
|
954 | 956 | raise HomeDirError(e) |
|
955 | 957 | return homedir |
|
956 | 958 | except HomeDirError: |
|
957 | 959 | raise |
|
958 | 960 | except: |
|
959 | 961 | return 'C:\\' |
|
960 | 962 | elif os.name == 'dos': |
|
961 | 963 | # Desperate, may do absurd things in classic MacOS. May work under DOS. |
|
962 | 964 | return 'C:\\' |
|
963 | 965 | else: |
|
964 | 966 | raise HomeDirError,'support for your operating system not implemented.' |
|
965 | 967 | |
|
966 | 968 | #**************************************************************************** |
|
967 | 969 | # strings and text |
|
968 | 970 | |
|
969 | 971 | class LSString(str): |
|
970 | 972 | """String derivative with a special access attributes. |
|
971 | 973 | |
|
972 | 974 | These are normal strings, but with the special attributes: |
|
973 | 975 | |
|
974 | 976 | .l (or .list) : value as list (split on newlines). |
|
975 | 977 | .n (or .nlstr): original value (the string itself). |
|
976 | 978 | .s (or .spstr): value as whitespace-separated string. |
|
977 | 979 | .p (or .paths): list of path objects |
|
978 | 980 | |
|
979 | 981 | Any values which require transformations are computed only once and |
|
980 | 982 | cached. |
|
981 | 983 | |
|
982 | 984 | Such strings are very useful to efficiently interact with the shell, which |
|
983 | 985 | typically only understands whitespace-separated options for commands.""" |
|
984 | 986 | |
|
985 | 987 | def get_list(self): |
|
986 | 988 | try: |
|
987 | 989 | return self.__list |
|
988 | 990 | except AttributeError: |
|
989 | 991 | self.__list = self.split('\n') |
|
990 | 992 | return self.__list |
|
991 | 993 | |
|
992 | 994 | l = list = property(get_list) |
|
993 | 995 | |
|
994 | 996 | def get_spstr(self): |
|
995 | 997 | try: |
|
996 | 998 | return self.__spstr |
|
997 | 999 | except AttributeError: |
|
998 | 1000 | self.__spstr = self.replace('\n',' ') |
|
999 | 1001 | return self.__spstr |
|
1000 | 1002 | |
|
1001 | 1003 | s = spstr = property(get_spstr) |
|
1002 | 1004 | |
|
1003 | 1005 | def get_nlstr(self): |
|
1004 | 1006 | return self |
|
1005 | 1007 | |
|
1006 | 1008 | n = nlstr = property(get_nlstr) |
|
1007 | 1009 | |
|
1008 | 1010 | def get_paths(self): |
|
1009 | 1011 | try: |
|
1010 | 1012 | return self.__paths |
|
1011 | 1013 | except AttributeError: |
|
1012 | 1014 | self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)] |
|
1013 | 1015 | return self.__paths |
|
1014 | 1016 | |
|
1015 | 1017 | p = paths = property(get_paths) |
|
1016 | 1018 | |
|
1017 | 1019 | def print_lsstring(arg): |
|
1018 | 1020 | """ Prettier (non-repr-like) and more informative printer for LSString """ |
|
1019 | 1021 | print "LSString (.p, .n, .l, .s available). Value:" |
|
1020 | 1022 | print arg |
|
1021 | 1023 | |
|
1022 | 1024 | print_lsstring = result_display.when_type(LSString)(print_lsstring) |
|
1023 | 1025 | |
|
1024 | 1026 | #---------------------------------------------------------------------------- |
|
1025 | 1027 | class SList(list): |
|
1026 | 1028 | """List derivative with a special access attributes. |
|
1027 | 1029 | |
|
1028 | 1030 | These are normal lists, but with the special attributes: |
|
1029 | 1031 | |
|
1030 | 1032 | .l (or .list) : value as list (the list itself). |
|
1031 | 1033 | .n (or .nlstr): value as a string, joined on newlines. |
|
1032 | 1034 | .s (or .spstr): value as a string, joined on spaces. |
|
1033 | 1035 | .p (or .paths): list of path objects |
|
1034 | 1036 | |
|
1035 | 1037 | Any values which require transformations are computed only once and |
|
1036 | 1038 | cached.""" |
|
1037 | 1039 | |
|
1038 | 1040 | def get_list(self): |
|
1039 | 1041 | return self |
|
1040 | 1042 | |
|
1041 | 1043 | l = list = property(get_list) |
|
1042 | 1044 | |
|
1043 | 1045 | def get_spstr(self): |
|
1044 | 1046 | try: |
|
1045 | 1047 | return self.__spstr |
|
1046 | 1048 | except AttributeError: |
|
1047 | 1049 | self.__spstr = ' '.join(self) |
|
1048 | 1050 | return self.__spstr |
|
1049 | 1051 | |
|
1050 | 1052 | s = spstr = property(get_spstr) |
|
1051 | 1053 | |
|
1052 | 1054 | def get_nlstr(self): |
|
1053 | 1055 | try: |
|
1054 | 1056 | return self.__nlstr |
|
1055 | 1057 | except AttributeError: |
|
1056 | 1058 | self.__nlstr = '\n'.join(self) |
|
1057 | 1059 | return self.__nlstr |
|
1058 | 1060 | |
|
1059 | 1061 | n = nlstr = property(get_nlstr) |
|
1060 | 1062 | |
|
1061 | 1063 | def get_paths(self): |
|
1062 | 1064 | try: |
|
1063 | 1065 | return self.__paths |
|
1064 | 1066 | except AttributeError: |
|
1065 | 1067 | self.__paths = [path(p) for p in self if os.path.exists(p)] |
|
1066 | 1068 | return self.__paths |
|
1067 | 1069 | |
|
1068 | 1070 | p = paths = property(get_paths) |
|
1069 | 1071 | |
|
1070 | 1072 | def grep(self, pattern, prune = False, field = None): |
|
1071 | 1073 | """ Return all strings matching 'pattern' (a regex or callable) |
|
1072 | 1074 | |
|
1073 | 1075 | This is case-insensitive. If prune is true, return all items |
|
1074 | 1076 | NOT matching the pattern. |
|
1075 | 1077 | |
|
1076 | 1078 | If field is specified, the match must occur in the specified |
|
1077 | 1079 | whitespace-separated field. |
|
1078 | 1080 | |
|
1079 | 1081 | Examples:: |
|
1080 | 1082 | |
|
1081 | 1083 | a.grep( lambda x: x.startswith('C') ) |
|
1082 | 1084 | a.grep('Cha.*log', prune=1) |
|
1083 | 1085 | a.grep('chm', field=-1) |
|
1084 | 1086 | """ |
|
1085 | 1087 | |
|
1086 | 1088 | def match_target(s): |
|
1087 | 1089 | if field is None: |
|
1088 | 1090 | return s |
|
1089 | 1091 | parts = s.split() |
|
1090 | 1092 | try: |
|
1091 | 1093 | tgt = parts[field] |
|
1092 | 1094 | return tgt |
|
1093 | 1095 | except IndexError: |
|
1094 | 1096 | return "" |
|
1095 | 1097 | |
|
1096 | 1098 | if isinstance(pattern, basestring): |
|
1097 | 1099 | pred = lambda x : re.search(pattern, x, re.IGNORECASE) |
|
1098 | 1100 | else: |
|
1099 | 1101 | pred = pattern |
|
1100 | 1102 | if not prune: |
|
1101 | 1103 | return SList([el for el in self if pred(match_target(el))]) |
|
1102 | 1104 | else: |
|
1103 | 1105 | return SList([el for el in self if not pred(match_target(el))]) |
|
1104 | 1106 | def fields(self, *fields): |
|
1105 | 1107 | """ Collect whitespace-separated fields from string list |
|
1106 | 1108 | |
|
1107 | 1109 | Allows quick awk-like usage of string lists. |
|
1108 | 1110 | |
|
1109 | 1111 | Example data (in var a, created by 'a = !ls -l'):: |
|
1110 | 1112 | -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog |
|
1111 | 1113 | drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython |
|
1112 | 1114 | |
|
1113 | 1115 | a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+'] |
|
1114 | 1116 | a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+'] |
|
1115 | 1117 | (note the joining by space). |
|
1116 | 1118 | a.fields(-1) is ['ChangeLog', 'IPython'] |
|
1117 | 1119 | |
|
1118 | 1120 | IndexErrors are ignored. |
|
1119 | 1121 | |
|
1120 | 1122 | Without args, fields() just split()'s the strings. |
|
1121 | 1123 | """ |
|
1122 | 1124 | if len(fields) == 0: |
|
1123 | 1125 | return [el.split() for el in self] |
|
1124 | 1126 | |
|
1125 | 1127 | res = SList() |
|
1126 | 1128 | for el in [f.split() for f in self]: |
|
1127 | 1129 | lineparts = [] |
|
1128 | 1130 | |
|
1129 | 1131 | for fd in fields: |
|
1130 | 1132 | try: |
|
1131 | 1133 | lineparts.append(el[fd]) |
|
1132 | 1134 | except IndexError: |
|
1133 | 1135 | pass |
|
1134 | 1136 | if lineparts: |
|
1135 | 1137 | res.append(" ".join(lineparts)) |
|
1136 | 1138 | |
|
1137 | 1139 | return res |
|
1138 | 1140 | |
|
1139 | 1141 | |
|
1140 | 1142 | |
|
1141 | 1143 | |
|
1142 | 1144 | |
|
1143 | 1145 | def print_slist(arg): |
|
1144 | 1146 | """ Prettier (non-repr-like) and more informative printer for SList """ |
|
1145 | 1147 | print "SList (.p, .n, .l, .s, .grep(), .fields() available). Value:" |
|
1146 | 1148 | nlprint(arg) |
|
1147 | 1149 | |
|
1148 | 1150 | print_slist = result_display.when_type(SList)(print_slist) |
|
1149 | 1151 | |
|
1150 | 1152 | |
|
1151 | 1153 | |
|
1152 | 1154 | #---------------------------------------------------------------------------- |
|
1153 | 1155 | def esc_quotes(strng): |
|
1154 | 1156 | """Return the input string with single and double quotes escaped out""" |
|
1155 | 1157 | |
|
1156 | 1158 | return strng.replace('"','\\"').replace("'","\\'") |
|
1157 | 1159 | |
|
1158 | 1160 | #---------------------------------------------------------------------------- |
|
1159 | 1161 | def make_quoted_expr(s): |
|
1160 | 1162 | """Return string s in appropriate quotes, using raw string if possible. |
|
1161 | 1163 | |
|
1162 | 1164 | Effectively this turns string: cd \ao\ao\ |
|
1163 | 1165 | to: r"cd \ao\ao\_"[:-1] |
|
1164 | 1166 | |
|
1165 | 1167 | Note the use of raw string and padding at the end to allow trailing backslash. |
|
1166 | 1168 | |
|
1167 | 1169 | """ |
|
1168 | 1170 | |
|
1169 | 1171 | tail = '' |
|
1170 | 1172 | tailpadding = '' |
|
1171 | 1173 | raw = '' |
|
1172 | 1174 | if "\\" in s: |
|
1173 | 1175 | raw = 'r' |
|
1174 | 1176 | if s.endswith('\\'): |
|
1175 | 1177 | tail = '[:-1]' |
|
1176 | 1178 | tailpadding = '_' |
|
1177 | 1179 | if '"' not in s: |
|
1178 | 1180 | quote = '"' |
|
1179 | 1181 | elif "'" not in s: |
|
1180 | 1182 | quote = "'" |
|
1181 | 1183 | elif '"""' not in s and not s.endswith('"'): |
|
1182 | 1184 | quote = '"""' |
|
1183 | 1185 | elif "'''" not in s and not s.endswith("'"): |
|
1184 | 1186 | quote = "'''" |
|
1185 | 1187 | else: |
|
1186 | 1188 | # give up, backslash-escaped string will do |
|
1187 | 1189 | return '"%s"' % esc_quotes(s) |
|
1188 | 1190 | res = raw + quote + s + tailpadding + quote + tail |
|
1189 | 1191 | return res |
|
1190 | 1192 | |
|
1191 | 1193 | |
|
1192 | 1194 | #---------------------------------------------------------------------------- |
|
1193 | 1195 | def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'): |
|
1194 | 1196 | """Take multiple lines of input. |
|
1195 | 1197 | |
|
1196 | 1198 | A list with each line of input as a separate element is returned when a |
|
1197 | 1199 | termination string is entered (defaults to a single '.'). Input can also |
|
1198 | 1200 | terminate via EOF (^D in Unix, ^Z-RET in Windows). |
|
1199 | 1201 | |
|
1200 | 1202 | Lines of input which end in \\ are joined into single entries (and a |
|
1201 | 1203 | secondary continuation prompt is issued as long as the user terminates |
|
1202 | 1204 | lines with \\). This allows entering very long strings which are still |
|
1203 | 1205 | meant to be treated as single entities. |
|
1204 | 1206 | """ |
|
1205 | 1207 | |
|
1206 | 1208 | try: |
|
1207 | 1209 | if header: |
|
1208 | 1210 | header += '\n' |
|
1209 | 1211 | lines = [raw_input(header + ps1)] |
|
1210 | 1212 | except EOFError: |
|
1211 | 1213 | return [] |
|
1212 | 1214 | terminate = [terminate_str] |
|
1213 | 1215 | try: |
|
1214 | 1216 | while lines[-1:] != terminate: |
|
1215 | 1217 | new_line = raw_input(ps1) |
|
1216 | 1218 | while new_line.endswith('\\'): |
|
1217 | 1219 | new_line = new_line[:-1] + raw_input(ps2) |
|
1218 | 1220 | lines.append(new_line) |
|
1219 | 1221 | |
|
1220 | 1222 | return lines[:-1] # don't return the termination command |
|
1221 | 1223 | except EOFError: |
|
1222 | 1224 | |
|
1223 | 1225 | return lines |
|
1224 | 1226 | |
|
1225 | 1227 | #---------------------------------------------------------------------------- |
|
1226 | 1228 | def raw_input_ext(prompt='', ps2='... '): |
|
1227 | 1229 | """Similar to raw_input(), but accepts extended lines if input ends with \\.""" |
|
1228 | 1230 | |
|
1229 | 1231 | line = raw_input(prompt) |
|
1230 | 1232 | while line.endswith('\\'): |
|
1231 | 1233 | line = line[:-1] + raw_input(ps2) |
|
1232 | 1234 | return line |
|
1233 | 1235 | |
|
1234 | 1236 | #---------------------------------------------------------------------------- |
|
1235 | 1237 | def ask_yes_no(prompt,default=None): |
|
1236 | 1238 | """Asks a question and returns a boolean (y/n) answer. |
|
1237 | 1239 | |
|
1238 | 1240 | If default is given (one of 'y','n'), it is used if the user input is |
|
1239 | 1241 | empty. Otherwise the question is repeated until an answer is given. |
|
1240 | 1242 | |
|
1241 | 1243 | An EOF is treated as the default answer. If there is no default, an |
|
1242 | 1244 | exception is raised to prevent infinite loops. |
|
1243 | 1245 | |
|
1244 | 1246 | Valid answers are: y/yes/n/no (match is not case sensitive).""" |
|
1245 | 1247 | |
|
1246 | 1248 | answers = {'y':True,'n':False,'yes':True,'no':False} |
|
1247 | 1249 | ans = None |
|
1248 | 1250 | while ans not in answers.keys(): |
|
1249 | 1251 | try: |
|
1250 | 1252 | ans = raw_input(prompt+' ').lower() |
|
1251 | 1253 | if not ans: # response was an empty string |
|
1252 | 1254 | ans = default |
|
1253 | 1255 | except KeyboardInterrupt: |
|
1254 | 1256 | pass |
|
1255 | 1257 | except EOFError: |
|
1256 | 1258 | if default in answers.keys(): |
|
1257 | 1259 | ans = default |
|
1258 | 1260 | |
|
1259 | 1261 | else: |
|
1260 | 1262 | raise |
|
1261 | 1263 | |
|
1262 | 1264 | return answers[ans] |
|
1263 | 1265 | |
|
1264 | 1266 | #---------------------------------------------------------------------------- |
|
1265 | 1267 | def marquee(txt='',width=78,mark='*'): |
|
1266 | 1268 | """Return the input string centered in a 'marquee'.""" |
|
1267 | 1269 | if not txt: |
|
1268 | 1270 | return (mark*width)[:width] |
|
1269 | 1271 | nmark = (width-len(txt)-2)/len(mark)/2 |
|
1270 | 1272 | if nmark < 0: nmark =0 |
|
1271 | 1273 | marks = mark*nmark |
|
1272 | 1274 | return '%s %s %s' % (marks,txt,marks) |
|
1273 | 1275 | |
|
1274 | 1276 | #---------------------------------------------------------------------------- |
|
1275 | 1277 | class EvalDict: |
|
1276 | 1278 | """ |
|
1277 | 1279 | Emulate a dict which evaluates its contents in the caller's frame. |
|
1278 | 1280 | |
|
1279 | 1281 | Usage: |
|
1280 | 1282 | >>>number = 19 |
|
1281 | 1283 | >>>text = "python" |
|
1282 | 1284 | >>>print "%(text.capitalize())s %(number/9.0).1f rules!" % EvalDict() |
|
1283 | 1285 | """ |
|
1284 | 1286 | |
|
1285 | 1287 | # This version is due to sismex01@hebmex.com on c.l.py, and is basically a |
|
1286 | 1288 | # modified (shorter) version of: |
|
1287 | 1289 | # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66018 by |
|
1288 | 1290 | # Skip Montanaro (skip@pobox.com). |
|
1289 | 1291 | |
|
1290 | 1292 | def __getitem__(self, name): |
|
1291 | 1293 | frame = sys._getframe(1) |
|
1292 | 1294 | return eval(name, frame.f_globals, frame.f_locals) |
|
1293 | 1295 | |
|
1294 | 1296 | EvalString = EvalDict # for backwards compatibility |
|
1295 | 1297 | #---------------------------------------------------------------------------- |
|
1296 | 1298 | def qw(words,flat=0,sep=None,maxsplit=-1): |
|
1297 | 1299 | """Similar to Perl's qw() operator, but with some more options. |
|
1298 | 1300 | |
|
1299 | 1301 | qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit) |
|
1300 | 1302 | |
|
1301 | 1303 | words can also be a list itself, and with flat=1, the output will be |
|
1302 | 1304 | recursively flattened. Examples: |
|
1303 | 1305 | |
|
1304 | 1306 | >>> qw('1 2') |
|
1305 | 1307 | ['1', '2'] |
|
1306 | 1308 | >>> qw(['a b','1 2',['m n','p q']]) |
|
1307 | 1309 | [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]] |
|
1308 | 1310 | >>> qw(['a b','1 2',['m n','p q']],flat=1) |
|
1309 | 1311 | ['a', 'b', '1', '2', 'm', 'n', 'p', 'q'] """ |
|
1310 | 1312 | |
|
1311 | 1313 | if type(words) in StringTypes: |
|
1312 | 1314 | return [word.strip() for word in words.split(sep,maxsplit) |
|
1313 | 1315 | if word and not word.isspace() ] |
|
1314 | 1316 | if flat: |
|
1315 | 1317 | return flatten(map(qw,words,[1]*len(words))) |
|
1316 | 1318 | return map(qw,words) |
|
1317 | 1319 | |
|
1318 | 1320 | #---------------------------------------------------------------------------- |
|
1319 | 1321 | def qwflat(words,sep=None,maxsplit=-1): |
|
1320 | 1322 | """Calls qw(words) in flat mode. It's just a convenient shorthand.""" |
|
1321 | 1323 | return qw(words,1,sep,maxsplit) |
|
1322 | 1324 | |
|
1323 | 1325 | #---------------------------------------------------------------------------- |
|
1324 | 1326 | def qw_lol(indata): |
|
1325 | 1327 | """qw_lol('a b') -> [['a','b']], |
|
1326 | 1328 | otherwise it's just a call to qw(). |
|
1327 | 1329 | |
|
1328 | 1330 | We need this to make sure the modules_some keys *always* end up as a |
|
1329 | 1331 | list of lists.""" |
|
1330 | 1332 | |
|
1331 | 1333 | if type(indata) in StringTypes: |
|
1332 | 1334 | return [qw(indata)] |
|
1333 | 1335 | else: |
|
1334 | 1336 | return qw(indata) |
|
1335 | 1337 | |
|
1336 | 1338 | #----------------------------------------------------------------------------- |
|
1337 | 1339 | def list_strings(arg): |
|
1338 | 1340 | """Always return a list of strings, given a string or list of strings |
|
1339 | 1341 | as input.""" |
|
1340 | 1342 | |
|
1341 | 1343 | if type(arg) in StringTypes: return [arg] |
|
1342 | 1344 | else: return arg |
|
1343 | 1345 | |
|
1344 | 1346 | #---------------------------------------------------------------------------- |
|
1345 | 1347 | def grep(pat,list,case=1): |
|
1346 | 1348 | """Simple minded grep-like function. |
|
1347 | 1349 | grep(pat,list) returns occurrences of pat in list, None on failure. |
|
1348 | 1350 | |
|
1349 | 1351 | It only does simple string matching, with no support for regexps. Use the |
|
1350 | 1352 | option case=0 for case-insensitive matching.""" |
|
1351 | 1353 | |
|
1352 | 1354 | # This is pretty crude. At least it should implement copying only references |
|
1353 | 1355 | # to the original data in case it's big. Now it copies the data for output. |
|
1354 | 1356 | out=[] |
|
1355 | 1357 | if case: |
|
1356 | 1358 | for term in list: |
|
1357 | 1359 | if term.find(pat)>-1: out.append(term) |
|
1358 | 1360 | else: |
|
1359 | 1361 | lpat=pat.lower() |
|
1360 | 1362 | for term in list: |
|
1361 | 1363 | if term.lower().find(lpat)>-1: out.append(term) |
|
1362 | 1364 | |
|
1363 | 1365 | if len(out): return out |
|
1364 | 1366 | else: return None |
|
1365 | 1367 | |
|
1366 | 1368 | #---------------------------------------------------------------------------- |
|
1367 | 1369 | def dgrep(pat,*opts): |
|
1368 | 1370 | """Return grep() on dir()+dir(__builtins__). |
|
1369 | 1371 | |
|
1370 | 1372 | A very common use of grep() when working interactively.""" |
|
1371 | 1373 | |
|
1372 | 1374 | return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts) |
|
1373 | 1375 | |
|
1374 | 1376 | #---------------------------------------------------------------------------- |
|
1375 | 1377 | def idgrep(pat): |
|
1376 | 1378 | """Case-insensitive dgrep()""" |
|
1377 | 1379 | |
|
1378 | 1380 | return dgrep(pat,0) |
|
1379 | 1381 | |
|
1380 | 1382 | #---------------------------------------------------------------------------- |
|
1381 | 1383 | def igrep(pat,list): |
|
1382 | 1384 | """Synonym for case-insensitive grep.""" |
|
1383 | 1385 | |
|
1384 | 1386 | return grep(pat,list,case=0) |
|
1385 | 1387 | |
|
1386 | 1388 | #---------------------------------------------------------------------------- |
|
1387 | 1389 | def indent(str,nspaces=4,ntabs=0): |
|
1388 | 1390 | """Indent a string a given number of spaces or tabstops. |
|
1389 | 1391 | |
|
1390 | 1392 | indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces. |
|
1391 | 1393 | """ |
|
1392 | 1394 | if str is None: |
|
1393 | 1395 | return |
|
1394 | 1396 | ind = '\t'*ntabs+' '*nspaces |
|
1395 | 1397 | outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind)) |
|
1396 | 1398 | if outstr.endswith(os.linesep+ind): |
|
1397 | 1399 | return outstr[:-len(ind)] |
|
1398 | 1400 | else: |
|
1399 | 1401 | return outstr |
|
1400 | 1402 | |
|
1401 | 1403 | #----------------------------------------------------------------------------- |
|
1402 | 1404 | def native_line_ends(filename,backup=1): |
|
1403 | 1405 | """Convert (in-place) a file to line-ends native to the current OS. |
|
1404 | 1406 | |
|
1405 | 1407 | If the optional backup argument is given as false, no backup of the |
|
1406 | 1408 | original file is left. """ |
|
1407 | 1409 | |
|
1408 | 1410 | backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'} |
|
1409 | 1411 | |
|
1410 | 1412 | bak_filename = filename + backup_suffixes[os.name] |
|
1411 | 1413 | |
|
1412 | 1414 | original = open(filename).read() |
|
1413 | 1415 | shutil.copy2(filename,bak_filename) |
|
1414 | 1416 | try: |
|
1415 | 1417 | new = open(filename,'wb') |
|
1416 | 1418 | new.write(os.linesep.join(original.splitlines())) |
|
1417 | 1419 | new.write(os.linesep) # ALWAYS put an eol at the end of the file |
|
1418 | 1420 | new.close() |
|
1419 | 1421 | except: |
|
1420 | 1422 | os.rename(bak_filename,filename) |
|
1421 | 1423 | if not backup: |
|
1422 | 1424 | try: |
|
1423 | 1425 | os.remove(bak_filename) |
|
1424 | 1426 | except: |
|
1425 | 1427 | pass |
|
1426 | 1428 | |
|
1427 | 1429 | #---------------------------------------------------------------------------- |
|
1428 | 1430 | def get_pager_cmd(pager_cmd = None): |
|
1429 | 1431 | """Return a pager command. |
|
1430 | 1432 | |
|
1431 | 1433 | Makes some attempts at finding an OS-correct one.""" |
|
1432 | 1434 | |
|
1433 | 1435 | if os.name == 'posix': |
|
1434 | 1436 | default_pager_cmd = 'less -r' # -r for color control sequences |
|
1435 | 1437 | elif os.name in ['nt','dos']: |
|
1436 | 1438 | default_pager_cmd = 'type' |
|
1437 | 1439 | |
|
1438 | 1440 | if pager_cmd is None: |
|
1439 | 1441 | try: |
|
1440 | 1442 | pager_cmd = os.environ['PAGER'] |
|
1441 | 1443 | except: |
|
1442 | 1444 | pager_cmd = default_pager_cmd |
|
1443 | 1445 | return pager_cmd |
|
1444 | 1446 | |
|
1445 | 1447 | #----------------------------------------------------------------------------- |
|
1446 | 1448 | def get_pager_start(pager,start): |
|
1447 | 1449 | """Return the string for paging files with an offset. |
|
1448 | 1450 | |
|
1449 | 1451 | This is the '+N' argument which less and more (under Unix) accept. |
|
1450 | 1452 | """ |
|
1451 | 1453 | |
|
1452 | 1454 | if pager in ['less','more']: |
|
1453 | 1455 | if start: |
|
1454 | 1456 | start_string = '+' + str(start) |
|
1455 | 1457 | else: |
|
1456 | 1458 | start_string = '' |
|
1457 | 1459 | else: |
|
1458 | 1460 | start_string = '' |
|
1459 | 1461 | return start_string |
|
1460 | 1462 | |
|
1461 | 1463 | #---------------------------------------------------------------------------- |
|
1462 | 1464 | # (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch() |
|
1463 | 1465 | if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs': |
|
1464 | 1466 | import msvcrt |
|
1465 | 1467 | def page_more(): |
|
1466 | 1468 | """ Smart pausing between pages |
|
1467 | 1469 | |
|
1468 | 1470 | @return: True if need print more lines, False if quit |
|
1469 | 1471 | """ |
|
1470 | 1472 | Term.cout.write('---Return to continue, q to quit--- ') |
|
1471 | 1473 | ans = msvcrt.getch() |
|
1472 | 1474 | if ans in ("q", "Q"): |
|
1473 | 1475 | result = False |
|
1474 | 1476 | else: |
|
1475 | 1477 | result = True |
|
1476 | 1478 | Term.cout.write("\b"*37 + " "*37 + "\b"*37) |
|
1477 | 1479 | return result |
|
1478 | 1480 | else: |
|
1479 | 1481 | def page_more(): |
|
1480 | 1482 | ans = raw_input('---Return to continue, q to quit--- ') |
|
1481 | 1483 | if ans.lower().startswith('q'): |
|
1482 | 1484 | return False |
|
1483 | 1485 | else: |
|
1484 | 1486 | return True |
|
1485 | 1487 | |
|
1486 | 1488 | esc_re = re.compile(r"(\x1b[^m]+m)") |
|
1487 | 1489 | |
|
1488 | 1490 | def page_dumb(strng,start=0,screen_lines=25): |
|
1489 | 1491 | """Very dumb 'pager' in Python, for when nothing else works. |
|
1490 | 1492 | |
|
1491 | 1493 | Only moves forward, same interface as page(), except for pager_cmd and |
|
1492 | 1494 | mode.""" |
|
1493 | 1495 | |
|
1494 | 1496 | out_ln = strng.splitlines()[start:] |
|
1495 | 1497 | screens = chop(out_ln,screen_lines-1) |
|
1496 | 1498 | if len(screens) == 1: |
|
1497 | 1499 | print >>Term.cout, os.linesep.join(screens[0]) |
|
1498 | 1500 | else: |
|
1499 | 1501 | last_escape = "" |
|
1500 | 1502 | for scr in screens[0:-1]: |
|
1501 | 1503 | hunk = os.linesep.join(scr) |
|
1502 | 1504 | print >>Term.cout, last_escape + hunk |
|
1503 | 1505 | if not page_more(): |
|
1504 | 1506 | return |
|
1505 | 1507 | esc_list = esc_re.findall(hunk) |
|
1506 | 1508 | if len(esc_list) > 0: |
|
1507 | 1509 | last_escape = esc_list[-1] |
|
1508 | 1510 | print >>Term.cout, last_escape + os.linesep.join(screens[-1]) |
|
1509 | 1511 | |
|
1510 | 1512 | #---------------------------------------------------------------------------- |
|
1511 | 1513 | def page(strng,start=0,screen_lines=0,pager_cmd = None): |
|
1512 | 1514 | """Print a string, piping through a pager after a certain length. |
|
1513 | 1515 | |
|
1514 | 1516 | The screen_lines parameter specifies the number of *usable* lines of your |
|
1515 | 1517 | terminal screen (total lines minus lines you need to reserve to show other |
|
1516 | 1518 | information). |
|
1517 | 1519 | |
|
1518 | 1520 | If you set screen_lines to a number <=0, page() will try to auto-determine |
|
1519 | 1521 | your screen size and will only use up to (screen_size+screen_lines) for |
|
1520 | 1522 | printing, paging after that. That is, if you want auto-detection but need |
|
1521 | 1523 | to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for |
|
1522 | 1524 | auto-detection without any lines reserved simply use screen_lines = 0. |
|
1523 | 1525 | |
|
1524 | 1526 | If a string won't fit in the allowed lines, it is sent through the |
|
1525 | 1527 | specified pager command. If none given, look for PAGER in the environment, |
|
1526 | 1528 | and ultimately default to less. |
|
1527 | 1529 | |
|
1528 | 1530 | If no system pager works, the string is sent through a 'dumb pager' |
|
1529 | 1531 | written in python, very simplistic. |
|
1530 | 1532 | """ |
|
1531 | 1533 | |
|
1532 | 1534 | # Some routines may auto-compute start offsets incorrectly and pass a |
|
1533 | 1535 | # negative value. Offset to 0 for robustness. |
|
1534 | 1536 | start = max(0,start) |
|
1535 | 1537 | |
|
1536 | 1538 | # first, try the hook |
|
1537 | 1539 | ip = IPython.ipapi.get() |
|
1538 | 1540 | if ip: |
|
1539 | 1541 | try: |
|
1540 | 1542 | ip.IP.hooks.show_in_pager(strng) |
|
1541 | 1543 | return |
|
1542 | 1544 | except IPython.ipapi.TryNext: |
|
1543 | 1545 | pass |
|
1544 | 1546 | |
|
1545 | 1547 | # Ugly kludge, but calling curses.initscr() flat out crashes in emacs |
|
1546 | 1548 | TERM = os.environ.get('TERM','dumb') |
|
1547 | 1549 | if TERM in ['dumb','emacs'] and os.name != 'nt': |
|
1548 | 1550 | print strng |
|
1549 | 1551 | return |
|
1550 | 1552 | # chop off the topmost part of the string we don't want to see |
|
1551 | 1553 | str_lines = strng.split(os.linesep)[start:] |
|
1552 | 1554 | str_toprint = os.linesep.join(str_lines) |
|
1553 | 1555 | num_newlines = len(str_lines) |
|
1554 | 1556 | len_str = len(str_toprint) |
|
1555 | 1557 | |
|
1556 | 1558 | # Dumb heuristics to guesstimate number of on-screen lines the string |
|
1557 | 1559 | # takes. Very basic, but good enough for docstrings in reasonable |
|
1558 | 1560 | # terminals. If someone later feels like refining it, it's not hard. |
|
1559 | 1561 | numlines = max(num_newlines,int(len_str/80)+1) |
|
1560 | 1562 | |
|
1561 | 1563 | if os.name == "nt": |
|
1562 | 1564 | screen_lines_def = get_console_size(defaulty=25)[1] |
|
1563 | 1565 | else: |
|
1564 | 1566 | screen_lines_def = 25 # default value if we can't auto-determine |
|
1565 | 1567 | |
|
1566 | 1568 | # auto-determine screen size |
|
1567 | 1569 | if screen_lines <= 0: |
|
1568 | 1570 | if TERM=='xterm': |
|
1569 | 1571 | use_curses = USE_CURSES |
|
1570 | 1572 | else: |
|
1571 | 1573 | # curses causes problems on many terminals other than xterm. |
|
1572 | 1574 | use_curses = False |
|
1573 | 1575 | if use_curses: |
|
1574 | 1576 | # There is a bug in curses, where *sometimes* it fails to properly |
|
1575 | 1577 | # initialize, and then after the endwin() call is made, the |
|
1576 | 1578 | # terminal is left in an unusable state. Rather than trying to |
|
1577 | 1579 | # check everytime for this (by requesting and comparing termios |
|
1578 | 1580 | # flags each time), we just save the initial terminal state and |
|
1579 | 1581 | # unconditionally reset it every time. It's cheaper than making |
|
1580 | 1582 | # the checks. |
|
1581 | 1583 | term_flags = termios.tcgetattr(sys.stdout) |
|
1582 | 1584 | scr = curses.initscr() |
|
1583 | 1585 | screen_lines_real,screen_cols = scr.getmaxyx() |
|
1584 | 1586 | curses.endwin() |
|
1585 | 1587 | # Restore terminal state in case endwin() didn't. |
|
1586 | 1588 | termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags) |
|
1587 | 1589 | # Now we have what we needed: the screen size in rows/columns |
|
1588 | 1590 | screen_lines += screen_lines_real |
|
1589 | 1591 | #print '***Screen size:',screen_lines_real,'lines x',\ |
|
1590 | 1592 | #screen_cols,'columns.' # dbg |
|
1591 | 1593 | else: |
|
1592 | 1594 | screen_lines += screen_lines_def |
|
1593 | 1595 | |
|
1594 | 1596 | #print 'numlines',numlines,'screenlines',screen_lines # dbg |
|
1595 | 1597 | if numlines <= screen_lines : |
|
1596 | 1598 | #print '*** normal print' # dbg |
|
1597 | 1599 | print >>Term.cout, str_toprint |
|
1598 | 1600 | else: |
|
1599 | 1601 | # Try to open pager and default to internal one if that fails. |
|
1600 | 1602 | # All failure modes are tagged as 'retval=1', to match the return |
|
1601 | 1603 | # value of a failed system command. If any intermediate attempt |
|
1602 | 1604 | # sets retval to 1, at the end we resort to our own page_dumb() pager. |
|
1603 | 1605 | pager_cmd = get_pager_cmd(pager_cmd) |
|
1604 | 1606 | pager_cmd += ' ' + get_pager_start(pager_cmd,start) |
|
1605 | 1607 | if os.name == 'nt': |
|
1606 | 1608 | if pager_cmd.startswith('type'): |
|
1607 | 1609 | # The default WinXP 'type' command is failing on complex strings. |
|
1608 | 1610 | retval = 1 |
|
1609 | 1611 | else: |
|
1610 | 1612 | tmpname = tempfile.mktemp('.txt') |
|
1611 | 1613 | tmpfile = file(tmpname,'wt') |
|
1612 | 1614 | tmpfile.write(strng) |
|
1613 | 1615 | tmpfile.close() |
|
1614 | 1616 | cmd = "%s < %s" % (pager_cmd,tmpname) |
|
1615 | 1617 | if os.system(cmd): |
|
1616 | 1618 | retval = 1 |
|
1617 | 1619 | else: |
|
1618 | 1620 | retval = None |
|
1619 | 1621 | os.remove(tmpname) |
|
1620 | 1622 | else: |
|
1621 | 1623 | try: |
|
1622 | 1624 | retval = None |
|
1623 | 1625 | # if I use popen4, things hang. No idea why. |
|
1624 | 1626 | #pager,shell_out = os.popen4(pager_cmd) |
|
1625 | 1627 | pager = os.popen(pager_cmd,'w') |
|
1626 | 1628 | pager.write(strng) |
|
1627 | 1629 | pager.close() |
|
1628 | 1630 | retval = pager.close() # success returns None |
|
1629 | 1631 | except IOError,msg: # broken pipe when user quits |
|
1630 | 1632 | if msg.args == (32,'Broken pipe'): |
|
1631 | 1633 | retval = None |
|
1632 | 1634 | else: |
|
1633 | 1635 | retval = 1 |
|
1634 | 1636 | except OSError: |
|
1635 | 1637 | # Other strange problems, sometimes seen in Win2k/cygwin |
|
1636 | 1638 | retval = 1 |
|
1637 | 1639 | if retval is not None: |
|
1638 | 1640 | page_dumb(strng,screen_lines=screen_lines) |
|
1639 | 1641 | |
|
1640 | 1642 | #---------------------------------------------------------------------------- |
|
1641 | 1643 | def page_file(fname,start = 0, pager_cmd = None): |
|
1642 | 1644 | """Page a file, using an optional pager command and starting line. |
|
1643 | 1645 | """ |
|
1644 | 1646 | |
|
1645 | 1647 | pager_cmd = get_pager_cmd(pager_cmd) |
|
1646 | 1648 | pager_cmd += ' ' + get_pager_start(pager_cmd,start) |
|
1647 | 1649 | |
|
1648 | 1650 | try: |
|
1649 | 1651 | if os.environ['TERM'] in ['emacs','dumb']: |
|
1650 | 1652 | raise EnvironmentError |
|
1651 | 1653 | xsys(pager_cmd + ' ' + fname) |
|
1652 | 1654 | except: |
|
1653 | 1655 | try: |
|
1654 | 1656 | if start > 0: |
|
1655 | 1657 | start -= 1 |
|
1656 | 1658 | page(open(fname).read(),start) |
|
1657 | 1659 | except: |
|
1658 | 1660 | print 'Unable to show file',`fname` |
|
1659 | 1661 | |
|
1660 | 1662 | |
|
1661 | 1663 | #---------------------------------------------------------------------------- |
|
1662 | 1664 | def snip_print(str,width = 75,print_full = 0,header = ''): |
|
1663 | 1665 | """Print a string snipping the midsection to fit in width. |
|
1664 | 1666 | |
|
1665 | 1667 | print_full: mode control: |
|
1666 | 1668 | - 0: only snip long strings |
|
1667 | 1669 | - 1: send to page() directly. |
|
1668 | 1670 | - 2: snip long strings and ask for full length viewing with page() |
|
1669 | 1671 | Return 1 if snipping was necessary, 0 otherwise.""" |
|
1670 | 1672 | |
|
1671 | 1673 | if print_full == 1: |
|
1672 | 1674 | page(header+str) |
|
1673 | 1675 | return 0 |
|
1674 | 1676 | |
|
1675 | 1677 | print header, |
|
1676 | 1678 | if len(str) < width: |
|
1677 | 1679 | print str |
|
1678 | 1680 | snip = 0 |
|
1679 | 1681 | else: |
|
1680 | 1682 | whalf = int((width -5)/2) |
|
1681 | 1683 | print str[:whalf] + ' <...> ' + str[-whalf:] |
|
1682 | 1684 | snip = 1 |
|
1683 | 1685 | if snip and print_full == 2: |
|
1684 | 1686 | if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y': |
|
1685 | 1687 | page(str) |
|
1686 | 1688 | return snip |
|
1687 | 1689 | |
|
1688 | 1690 | #**************************************************************************** |
|
1689 | 1691 | # lists, dicts and structures |
|
1690 | 1692 | |
|
1691 | 1693 | def belong(candidates,checklist): |
|
1692 | 1694 | """Check whether a list of items appear in a given list of options. |
|
1693 | 1695 | |
|
1694 | 1696 | Returns a list of 1 and 0, one for each candidate given.""" |
|
1695 | 1697 | |
|
1696 | 1698 | return [x in checklist for x in candidates] |
|
1697 | 1699 | |
|
1698 | 1700 | #---------------------------------------------------------------------------- |
|
1699 | 1701 | def uniq_stable(elems): |
|
1700 | 1702 | """uniq_stable(elems) -> list |
|
1701 | 1703 | |
|
1702 | 1704 | Return from an iterable, a list of all the unique elements in the input, |
|
1703 | 1705 | but maintaining the order in which they first appear. |
|
1704 | 1706 | |
|
1705 | 1707 | A naive solution to this problem which just makes a dictionary with the |
|
1706 | 1708 | elements as keys fails to respect the stability condition, since |
|
1707 | 1709 | dictionaries are unsorted by nature. |
|
1708 | 1710 | |
|
1709 | 1711 | Note: All elements in the input must be valid dictionary keys for this |
|
1710 | 1712 | routine to work, as it internally uses a dictionary for efficiency |
|
1711 | 1713 | reasons.""" |
|
1712 | 1714 | |
|
1713 | 1715 | unique = [] |
|
1714 | 1716 | unique_dict = {} |
|
1715 | 1717 | for nn in elems: |
|
1716 | 1718 | if nn not in unique_dict: |
|
1717 | 1719 | unique.append(nn) |
|
1718 | 1720 | unique_dict[nn] = None |
|
1719 | 1721 | return unique |
|
1720 | 1722 | |
|
1721 | 1723 | #---------------------------------------------------------------------------- |
|
1722 | 1724 | class NLprinter: |
|
1723 | 1725 | """Print an arbitrarily nested list, indicating index numbers. |
|
1724 | 1726 | |
|
1725 | 1727 | An instance of this class called nlprint is available and callable as a |
|
1726 | 1728 | function. |
|
1727 | 1729 | |
|
1728 | 1730 | nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent' |
|
1729 | 1731 | and using 'sep' to separate the index from the value. """ |
|
1730 | 1732 | |
|
1731 | 1733 | def __init__(self): |
|
1732 | 1734 | self.depth = 0 |
|
1733 | 1735 | |
|
1734 | 1736 | def __call__(self,lst,pos='',**kw): |
|
1735 | 1737 | """Prints the nested list numbering levels.""" |
|
1736 | 1738 | kw.setdefault('indent',' ') |
|
1737 | 1739 | kw.setdefault('sep',': ') |
|
1738 | 1740 | kw.setdefault('start',0) |
|
1739 | 1741 | kw.setdefault('stop',len(lst)) |
|
1740 | 1742 | # we need to remove start and stop from kw so they don't propagate |
|
1741 | 1743 | # into a recursive call for a nested list. |
|
1742 | 1744 | start = kw['start']; del kw['start'] |
|
1743 | 1745 | stop = kw['stop']; del kw['stop'] |
|
1744 | 1746 | if self.depth == 0 and 'header' in kw.keys(): |
|
1745 | 1747 | print kw['header'] |
|
1746 | 1748 | |
|
1747 | 1749 | for idx in range(start,stop): |
|
1748 | 1750 | elem = lst[idx] |
|
1749 | 1751 | if type(elem)==type([]): |
|
1750 | 1752 | self.depth += 1 |
|
1751 | 1753 | self.__call__(elem,itpl('$pos$idx,'),**kw) |
|
1752 | 1754 | self.depth -= 1 |
|
1753 | 1755 | else: |
|
1754 | 1756 | printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem') |
|
1755 | 1757 | |
|
1756 | 1758 | nlprint = NLprinter() |
|
1757 | 1759 | #---------------------------------------------------------------------------- |
|
1758 | 1760 | def all_belong(candidates,checklist): |
|
1759 | 1761 | """Check whether a list of items ALL appear in a given list of options. |
|
1760 | 1762 | |
|
1761 | 1763 | Returns a single 1 or 0 value.""" |
|
1762 | 1764 | |
|
1763 | 1765 | return 1-(0 in [x in checklist for x in candidates]) |
|
1764 | 1766 | |
|
1765 | 1767 | #---------------------------------------------------------------------------- |
|
1766 | 1768 | def sort_compare(lst1,lst2,inplace = 1): |
|
1767 | 1769 | """Sort and compare two lists. |
|
1768 | 1770 | |
|
1769 | 1771 | By default it does it in place, thus modifying the lists. Use inplace = 0 |
|
1770 | 1772 | to avoid that (at the cost of temporary copy creation).""" |
|
1771 | 1773 | if not inplace: |
|
1772 | 1774 | lst1 = lst1[:] |
|
1773 | 1775 | lst2 = lst2[:] |
|
1774 | 1776 | lst1.sort(); lst2.sort() |
|
1775 | 1777 | return lst1 == lst2 |
|
1776 | 1778 | |
|
1777 | 1779 | #---------------------------------------------------------------------------- |
|
1778 | 1780 | def mkdict(**kwargs): |
|
1779 | 1781 | """Return a dict from a keyword list. |
|
1780 | 1782 | |
|
1781 | 1783 | It's just syntactic sugar for making ditcionary creation more convenient: |
|
1782 | 1784 | # the standard way |
|
1783 | 1785 | >>>data = { 'red' : 1, 'green' : 2, 'blue' : 3 } |
|
1784 | 1786 | # a cleaner way |
|
1785 | 1787 | >>>data = dict(red=1, green=2, blue=3) |
|
1786 | 1788 | |
|
1787 | 1789 | If you need more than this, look at the Struct() class.""" |
|
1788 | 1790 | |
|
1789 | 1791 | return kwargs |
|
1790 | 1792 | |
|
1791 | 1793 | #---------------------------------------------------------------------------- |
|
1792 | 1794 | def list2dict(lst): |
|
1793 | 1795 | """Takes a list of (key,value) pairs and turns it into a dict.""" |
|
1794 | 1796 | |
|
1795 | 1797 | dic = {} |
|
1796 | 1798 | for k,v in lst: dic[k] = v |
|
1797 | 1799 | return dic |
|
1798 | 1800 | |
|
1799 | 1801 | #---------------------------------------------------------------------------- |
|
1800 | 1802 | def list2dict2(lst,default=''): |
|
1801 | 1803 | """Takes a list and turns it into a dict. |
|
1802 | 1804 | Much slower than list2dict, but more versatile. This version can take |
|
1803 | 1805 | lists with sublists of arbitrary length (including sclars).""" |
|
1804 | 1806 | |
|
1805 | 1807 | dic = {} |
|
1806 | 1808 | for elem in lst: |
|
1807 | 1809 | if type(elem) in (types.ListType,types.TupleType): |
|
1808 | 1810 | size = len(elem) |
|
1809 | 1811 | if size == 0: |
|
1810 | 1812 | pass |
|
1811 | 1813 | elif size == 1: |
|
1812 | 1814 | dic[elem] = default |
|
1813 | 1815 | else: |
|
1814 | 1816 | k,v = elem[0], elem[1:] |
|
1815 | 1817 | if len(v) == 1: v = v[0] |
|
1816 | 1818 | dic[k] = v |
|
1817 | 1819 | else: |
|
1818 | 1820 | dic[elem] = default |
|
1819 | 1821 | return dic |
|
1820 | 1822 | |
|
1821 | 1823 | #---------------------------------------------------------------------------- |
|
1822 | 1824 | def flatten(seq): |
|
1823 | 1825 | """Flatten a list of lists (NOT recursive, only works for 2d lists).""" |
|
1824 | 1826 | |
|
1825 | 1827 | return [x for subseq in seq for x in subseq] |
|
1826 | 1828 | |
|
1827 | 1829 | #---------------------------------------------------------------------------- |
|
1828 | 1830 | def get_slice(seq,start=0,stop=None,step=1): |
|
1829 | 1831 | """Get a slice of a sequence with variable step. Specify start,stop,step.""" |
|
1830 | 1832 | if stop == None: |
|
1831 | 1833 | stop = len(seq) |
|
1832 | 1834 | item = lambda i: seq[i] |
|
1833 | 1835 | return map(item,xrange(start,stop,step)) |
|
1834 | 1836 | |
|
1835 | 1837 | #---------------------------------------------------------------------------- |
|
1836 | 1838 | def chop(seq,size): |
|
1837 | 1839 | """Chop a sequence into chunks of the given size.""" |
|
1838 | 1840 | chunk = lambda i: seq[i:i+size] |
|
1839 | 1841 | return map(chunk,xrange(0,len(seq),size)) |
|
1840 | 1842 | |
|
1841 | 1843 | #---------------------------------------------------------------------------- |
|
1842 | 1844 | # with is a keyword as of python 2.5, so this function is renamed to withobj |
|
1843 | 1845 | # from its old 'with' name. |
|
1844 | 1846 | def with_obj(object, **args): |
|
1845 | 1847 | """Set multiple attributes for an object, similar to Pascal's with. |
|
1846 | 1848 | |
|
1847 | 1849 | Example: |
|
1848 | 1850 | with_obj(jim, |
|
1849 | 1851 | born = 1960, |
|
1850 | 1852 | haircolour = 'Brown', |
|
1851 | 1853 | eyecolour = 'Green') |
|
1852 | 1854 | |
|
1853 | 1855 | Credit: Greg Ewing, in |
|
1854 | 1856 | http://mail.python.org/pipermail/python-list/2001-May/040703.html. |
|
1855 | 1857 | |
|
1856 | 1858 | NOTE: up until IPython 0.7.2, this was called simply 'with', but 'with' |
|
1857 | 1859 | has become a keyword for Python 2.5, so we had to rename it.""" |
|
1858 | 1860 | |
|
1859 | 1861 | object.__dict__.update(args) |
|
1860 | 1862 | |
|
1861 | 1863 | #---------------------------------------------------------------------------- |
|
1862 | 1864 | def setattr_list(obj,alist,nspace = None): |
|
1863 | 1865 | """Set a list of attributes for an object taken from a namespace. |
|
1864 | 1866 | |
|
1865 | 1867 | setattr_list(obj,alist,nspace) -> sets in obj all the attributes listed in |
|
1866 | 1868 | alist with their values taken from nspace, which must be a dict (something |
|
1867 | 1869 | like locals() will often do) If nspace isn't given, locals() of the |
|
1868 | 1870 | *caller* is used, so in most cases you can omit it. |
|
1869 | 1871 | |
|
1870 | 1872 | Note that alist can be given as a string, which will be automatically |
|
1871 | 1873 | split into a list on whitespace. If given as a list, it must be a list of |
|
1872 | 1874 | *strings* (the variable names themselves), not of variables.""" |
|
1873 | 1875 | |
|
1874 | 1876 | # this grabs the local variables from the *previous* call frame -- that is |
|
1875 | 1877 | # the locals from the function that called setattr_list(). |
|
1876 | 1878 | # - snipped from weave.inline() |
|
1877 | 1879 | if nspace is None: |
|
1878 | 1880 | call_frame = sys._getframe().f_back |
|
1879 | 1881 | nspace = call_frame.f_locals |
|
1880 | 1882 | |
|
1881 | 1883 | if type(alist) in StringTypes: |
|
1882 | 1884 | alist = alist.split() |
|
1883 | 1885 | for attr in alist: |
|
1884 | 1886 | val = eval(attr,nspace) |
|
1885 | 1887 | setattr(obj,attr,val) |
|
1886 | 1888 | |
|
1887 | 1889 | #---------------------------------------------------------------------------- |
|
1888 | 1890 | def getattr_list(obj,alist,*args): |
|
1889 | 1891 | """getattr_list(obj,alist[, default]) -> attribute list. |
|
1890 | 1892 | |
|
1891 | 1893 | Get a list of named attributes for an object. When a default argument is |
|
1892 | 1894 | given, it is returned when the attribute doesn't exist; without it, an |
|
1893 | 1895 | exception is raised in that case. |
|
1894 | 1896 | |
|
1895 | 1897 | Note that alist can be given as a string, which will be automatically |
|
1896 | 1898 | split into a list on whitespace. If given as a list, it must be a list of |
|
1897 | 1899 | *strings* (the variable names themselves), not of variables.""" |
|
1898 | 1900 | |
|
1899 | 1901 | if type(alist) in StringTypes: |
|
1900 | 1902 | alist = alist.split() |
|
1901 | 1903 | if args: |
|
1902 | 1904 | if len(args)==1: |
|
1903 | 1905 | default = args[0] |
|
1904 | 1906 | return map(lambda attr: getattr(obj,attr,default),alist) |
|
1905 | 1907 | else: |
|
1906 | 1908 | raise ValueError,'getattr_list() takes only one optional argument' |
|
1907 | 1909 | else: |
|
1908 | 1910 | return map(lambda attr: getattr(obj,attr),alist) |
|
1909 | 1911 | |
|
1910 | 1912 | #---------------------------------------------------------------------------- |
|
1911 | 1913 | def map_method(method,object_list,*argseq,**kw): |
|
1912 | 1914 | """map_method(method,object_list,*args,**kw) -> list |
|
1913 | 1915 | |
|
1914 | 1916 | Return a list of the results of applying the methods to the items of the |
|
1915 | 1917 | argument sequence(s). If more than one sequence is given, the method is |
|
1916 | 1918 | called with an argument list consisting of the corresponding item of each |
|
1917 | 1919 | sequence. All sequences must be of the same length. |
|
1918 | 1920 | |
|
1919 | 1921 | Keyword arguments are passed verbatim to all objects called. |
|
1920 | 1922 | |
|
1921 | 1923 | This is Python code, so it's not nearly as fast as the builtin map().""" |
|
1922 | 1924 | |
|
1923 | 1925 | out_list = [] |
|
1924 | 1926 | idx = 0 |
|
1925 | 1927 | for object in object_list: |
|
1926 | 1928 | try: |
|
1927 | 1929 | handler = getattr(object, method) |
|
1928 | 1930 | except AttributeError: |
|
1929 | 1931 | out_list.append(None) |
|
1930 | 1932 | else: |
|
1931 | 1933 | if argseq: |
|
1932 | 1934 | args = map(lambda lst:lst[idx],argseq) |
|
1933 | 1935 | #print 'ob',object,'hand',handler,'ar',args # dbg |
|
1934 | 1936 | out_list.append(handler(args,**kw)) |
|
1935 | 1937 | else: |
|
1936 | 1938 | out_list.append(handler(**kw)) |
|
1937 | 1939 | idx += 1 |
|
1938 | 1940 | return out_list |
|
1939 | 1941 | |
|
1940 | 1942 | #---------------------------------------------------------------------------- |
|
1941 | 1943 | def get_class_members(cls): |
|
1942 | 1944 | ret = dir(cls) |
|
1943 | 1945 | if hasattr(cls,'__bases__'): |
|
1944 | 1946 | for base in cls.__bases__: |
|
1945 | 1947 | ret.extend(get_class_members(base)) |
|
1946 | 1948 | return ret |
|
1947 | 1949 | |
|
1948 | 1950 | #---------------------------------------------------------------------------- |
|
1949 | 1951 | def dir2(obj): |
|
1950 | 1952 | """dir2(obj) -> list of strings |
|
1951 | 1953 | |
|
1952 | 1954 | Extended version of the Python builtin dir(), which does a few extra |
|
1953 | 1955 | checks, and supports common objects with unusual internals that confuse |
|
1954 | 1956 | dir(), such as Traits and PyCrust. |
|
1955 | 1957 | |
|
1956 | 1958 | This version is guaranteed to return only a list of true strings, whereas |
|
1957 | 1959 | dir() returns anything that objects inject into themselves, even if they |
|
1958 | 1960 | are later not really valid for attribute access (many extension libraries |
|
1959 | 1961 | have such bugs). |
|
1960 | 1962 | """ |
|
1961 | 1963 | |
|
1962 | 1964 | # Start building the attribute list via dir(), and then complete it |
|
1963 | 1965 | # with a few extra special-purpose calls. |
|
1964 | 1966 | words = dir(obj) |
|
1965 | 1967 | |
|
1966 | 1968 | if hasattr(obj,'__class__'): |
|
1967 | 1969 | words.append('__class__') |
|
1968 | 1970 | words.extend(get_class_members(obj.__class__)) |
|
1969 | 1971 | #if '__base__' in words: 1/0 |
|
1970 | 1972 | |
|
1971 | 1973 | # Some libraries (such as traits) may introduce duplicates, we want to |
|
1972 | 1974 | # track and clean this up if it happens |
|
1973 | 1975 | may_have_dupes = False |
|
1974 | 1976 | |
|
1975 | 1977 | # this is the 'dir' function for objects with Enthought's traits |
|
1976 | 1978 | if hasattr(obj, 'trait_names'): |
|
1977 | 1979 | try: |
|
1978 | 1980 | words.extend(obj.trait_names()) |
|
1979 | 1981 | may_have_dupes = True |
|
1980 | 1982 | except TypeError: |
|
1981 | 1983 | # This will happen if `obj` is a class and not an instance. |
|
1982 | 1984 | pass |
|
1983 | 1985 | |
|
1984 | 1986 | # Support for PyCrust-style _getAttributeNames magic method. |
|
1985 | 1987 | if hasattr(obj, '_getAttributeNames'): |
|
1986 | 1988 | try: |
|
1987 | 1989 | words.extend(obj._getAttributeNames()) |
|
1988 | 1990 | may_have_dupes = True |
|
1989 | 1991 | except TypeError: |
|
1990 | 1992 | # `obj` is a class and not an instance. Ignore |
|
1991 | 1993 | # this error. |
|
1992 | 1994 | pass |
|
1993 | 1995 | |
|
1994 | 1996 | if may_have_dupes: |
|
1995 | 1997 | # eliminate possible duplicates, as some traits may also |
|
1996 | 1998 | # appear as normal attributes in the dir() call. |
|
1997 | 1999 | words = list(set(words)) |
|
1998 | 2000 | words.sort() |
|
1999 | 2001 | |
|
2000 | 2002 | # filter out non-string attributes which may be stuffed by dir() calls |
|
2001 | 2003 | # and poor coding in third-party modules |
|
2002 | 2004 | return [w for w in words if isinstance(w, basestring)] |
|
2003 | 2005 | |
|
2004 | 2006 | #---------------------------------------------------------------------------- |
|
2005 | 2007 | def import_fail_info(mod_name,fns=None): |
|
2006 | 2008 | """Inform load failure for a module.""" |
|
2007 | 2009 | |
|
2008 | 2010 | if fns == None: |
|
2009 | 2011 | warn("Loading of %s failed.\n" % (mod_name,)) |
|
2010 | 2012 | else: |
|
2011 | 2013 | warn("Loading of %s from %s failed.\n" % (fns,mod_name)) |
|
2012 | 2014 | |
|
2013 | 2015 | #---------------------------------------------------------------------------- |
|
2014 | 2016 | # Proposed popitem() extension, written as a method |
|
2015 | 2017 | |
|
2016 | 2018 | |
|
2017 | 2019 | class NotGiven: pass |
|
2018 | 2020 | |
|
2019 | 2021 | def popkey(dct,key,default=NotGiven): |
|
2020 | 2022 | """Return dct[key] and delete dct[key]. |
|
2021 | 2023 | |
|
2022 | 2024 | If default is given, return it if dct[key] doesn't exist, otherwise raise |
|
2023 | 2025 | KeyError. """ |
|
2024 | 2026 | |
|
2025 | 2027 | try: |
|
2026 | 2028 | val = dct[key] |
|
2027 | 2029 | except KeyError: |
|
2028 | 2030 | if default is NotGiven: |
|
2029 | 2031 | raise |
|
2030 | 2032 | else: |
|
2031 | 2033 | return default |
|
2032 | 2034 | else: |
|
2033 | 2035 | del dct[key] |
|
2034 | 2036 | return val |
|
2035 | 2037 | |
|
2036 | 2038 | def wrap_deprecated(func, suggest = '<nothing>'): |
|
2037 | 2039 | def newFunc(*args, **kwargs): |
|
2038 | 2040 | warnings.warn("Call to deprecated function %s, use %s instead" % |
|
2039 | 2041 | ( func.__name__, suggest), |
|
2040 | 2042 | category=DeprecationWarning, |
|
2041 | 2043 | stacklevel = 2) |
|
2042 | 2044 | return func(*args, **kwargs) |
|
2043 | 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 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """Mimic C structs with lots of extra functionality. |
|
3 | 3 | |
|
4 | 4 | $Id: ipstruct.py 1950 2006-11-28 19:15:35Z vivainio $""" |
|
5 | 5 | |
|
6 | 6 | #***************************************************************************** |
|
7 | 7 | # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu> |
|
8 | 8 | # |
|
9 | 9 | # Distributed under the terms of the BSD License. The full license is in |
|
10 | 10 | # the file COPYING, distributed as part of this software. |
|
11 | 11 | #***************************************************************************** |
|
12 | 12 | |
|
13 | 13 | from IPython import Release |
|
14 | 14 | __author__ = '%s <%s>' % Release.authors['Fernando'] |
|
15 | 15 | __license__ = Release.license |
|
16 | 16 | |
|
17 | 17 | __all__ = ['Struct'] |
|
18 | 18 | |
|
19 | 19 | import types |
|
20 | 20 | import pprint |
|
21 | 21 | |
|
22 | 22 | from IPython.genutils import list2dict2 |
|
23 | 23 | |
|
24 | 24 | class Struct: |
|
25 | 25 | """Class to mimic C structs but also provide convenient dictionary-like |
|
26 | 26 | functionality. |
|
27 | 27 | |
|
28 | 28 | Instances can be initialized with a dictionary, a list of key=value pairs |
|
29 | 29 | or both. If both are present, the dictionary must come first. |
|
30 | 30 | |
|
31 | 31 | Because Python classes provide direct assignment to their members, it's |
|
32 | 32 | easy to overwrite normal methods (S.copy = 1 would destroy access to |
|
33 | 33 | S.copy()). For this reason, all builtin method names are protected and |
|
34 | 34 | can't be assigned to. An attempt to do s.copy=1 or s['copy']=1 will raise |
|
35 | 35 | a KeyError exception. If you really want to, you can bypass this |
|
36 | 36 | protection by directly assigning to __dict__: s.__dict__['copy']=1 will |
|
37 | 37 | still work. Doing this will break functionality, though. As in most of |
|
38 | 38 | Python, namespace protection is weakly enforced, so feel free to shoot |
|
39 | 39 | yourself if you really want to. |
|
40 | 40 | |
|
41 | 41 | Note that this class uses more memory and is *much* slower than a regular |
|
42 | 42 | dictionary, so be careful in situations where memory or performance are |
|
43 | 43 | critical. But for day to day use it should behave fine. It is particularly |
|
44 | 44 | convenient for storing configuration data in programs. |
|
45 | 45 | |
|
46 | 46 | +,+=,- and -= are implemented. +/+= do merges (non-destructive updates), |
|
47 | 47 | -/-= remove keys from the original. See the method descripitions. |
|
48 | 48 | |
|
49 | 49 | This class allows a quick access syntax: both s.key and s['key'] are |
|
50 | 50 | valid. This syntax has a limitation: each 'key' has to be explicitly |
|
51 | 51 | accessed by its original name. The normal s.key syntax doesn't provide |
|
52 | 52 | access to the keys via variables whose values evaluate to the desired |
|
53 | 53 | keys. An example should clarify this: |
|
54 | 54 | |
|
55 | 55 | Define a dictionary and initialize both with dict and k=v pairs: |
|
56 | 56 | >>> d={'a':1,'b':2} |
|
57 | 57 | >>> s=Struct(d,hi=10,ho=20) |
|
58 | ||
|
58 | 59 | The return of __repr__ can be used to create a new instance: |
|
59 | 60 | >>> s |
|
60 |
Struct({' |
|
|
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 | 65 | __str__ (called by print) shows it's not quite a regular dictionary: |
|
62 | 66 | >>> print s |
|
63 |
Struct |
|
|
67 | Struct({'__allownew': True, 'a': 1, 'b': 2, 'hi': 10, 'ho': 20}) | |
|
68 | ||
|
64 | 69 | Access by explicitly named key with dot notation: |
|
65 | 70 | >>> s.a |
|
66 | 71 | 1 |
|
72 | ||
|
67 | 73 | Or like a dictionary: |
|
68 | 74 | >>> s['a'] |
|
69 | 75 | 1 |
|
76 | ||
|
70 | 77 | If you want a variable to hold the key value, only dictionary access works: |
|
71 | 78 | >>> key='hi' |
|
72 | 79 | >>> s.key |
|
73 | 80 | Traceback (most recent call last): |
|
74 | 81 | File "<stdin>", line 1, in ? |
|
75 | 82 | AttributeError: Struct instance has no attribute 'key' |
|
83 | ||
|
76 | 84 | >>> s[key] |
|
77 | 85 | 10 |
|
78 | 86 | |
|
79 | 87 | Another limitation of the s.key syntax (and Struct(key=val) |
|
80 | 88 | initialization): keys can't be numbers. But numeric keys can be used and |
|
81 | 89 | accessed using the dictionary syntax. Again, an example: |
|
82 | 90 | |
|
83 | 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 | 95 | SyntaxError: keyword can't be an expression |
|
96 | ||
|
86 | 97 | But this does: |
|
87 | 98 | >>> s=Struct() |
|
88 | 99 | >>> s[4]='hi' |
|
89 | 100 | >>> s |
|
90 | Struct({4: 'hi'}) | |
|
101 | Struct({4: 'hi', '__allownew': True}) | |
|
91 | 102 | >>> s[4] |
|
92 | 103 | 'hi' |
|
93 | 104 | """ |
|
94 | 105 | |
|
95 | 106 | # Attributes to which __setitem__ and __setattr__ will block access. |
|
96 | 107 | # Note: much of this will be moot in Python 2.2 and will be done in a much |
|
97 | 108 | # cleaner way. |
|
98 | 109 | __protected = ('copy dict dictcopy get has_attr has_key items keys ' |
|
99 | 110 | 'merge popitem setdefault update values ' |
|
100 | 111 | '__make_dict __dict_invert ').split() |
|
101 | 112 | |
|
102 | 113 | def __init__(self,dict=None,**kw): |
|
103 | 114 | """Initialize with a dictionary, another Struct, or by giving |
|
104 | 115 | explicitly the list of attributes. |
|
105 | 116 | |
|
106 | 117 | Both can be used, but the dictionary must come first: |
|
107 | 118 | Struct(dict), Struct(k1=v1,k2=v2) or Struct(dict,k1=v1,k2=v2). |
|
108 | 119 | """ |
|
109 | 120 | self.__dict__['__allownew'] = True |
|
110 | 121 | if dict is None: |
|
111 | 122 | dict = {} |
|
112 | 123 | if isinstance(dict,Struct): |
|
113 | 124 | dict = dict.dict() |
|
114 | 125 | elif dict and type(dict) is not types.DictType: |
|
115 | 126 | raise TypeError,\ |
|
116 | 127 | 'Initialize with a dictionary or key=val pairs.' |
|
117 | 128 | dict.update(kw) |
|
118 | 129 | # do the updating by hand to guarantee that we go through the |
|
119 | 130 | # safety-checked __setitem__ |
|
120 | 131 | for k,v in dict.items(): |
|
121 | 132 | self[k] = v |
|
122 | 133 | |
|
123 | 134 | |
|
124 | 135 | def __setitem__(self,key,value): |
|
125 | 136 | """Used when struct[key] = val calls are made.""" |
|
126 | 137 | if key in Struct.__protected: |
|
127 | 138 | raise KeyError,'Key '+`key`+' is a protected key of class Struct.' |
|
128 | 139 | if not self['__allownew'] and key not in self.__dict__: |
|
129 | 140 | raise KeyError( |
|
130 | 141 | "Can't create unknown attribute %s - Check for typos, or use allow_new_attr to create new attributes!" % |
|
131 | 142 | key) |
|
132 | 143 | |
|
133 | 144 | self.__dict__[key] = value |
|
134 | 145 | |
|
135 | 146 | def __setattr__(self, key, value): |
|
136 | 147 | """Used when struct.key = val calls are made.""" |
|
137 | 148 | self.__setitem__(key,value) |
|
138 | 149 | |
|
139 | 150 | def __str__(self): |
|
140 | 151 | """Gets called by print.""" |
|
141 | 152 | |
|
142 | 153 | return 'Struct('+ pprint.pformat(self.__dict__)+')' |
|
143 | 154 | |
|
144 | 155 | def __repr__(self): |
|
145 | 156 | """Gets called by repr. |
|
146 | 157 | |
|
147 | 158 | A Struct can be recreated with S_new=eval(repr(S_old)).""" |
|
148 | 159 | return self.__str__() |
|
149 | 160 | |
|
150 | 161 | def __getitem__(self,key): |
|
151 | 162 | """Allows struct[key] access.""" |
|
152 | 163 | return self.__dict__[key] |
|
153 | 164 | |
|
154 | 165 | def __contains__(self,key): |
|
155 | 166 | """Allows use of the 'in' operator.""" |
|
156 | 167 | return self.__dict__.has_key(key) |
|
157 | 168 | |
|
158 | 169 | def __iadd__(self,other): |
|
159 | 170 | """S += S2 is a shorthand for S.merge(S2).""" |
|
160 | 171 | self.merge(other) |
|
161 | 172 | return self |
|
162 | 173 | |
|
163 | 174 | def __add__(self,other): |
|
164 | 175 | """S + S2 -> New Struct made form S and S.merge(S2)""" |
|
165 | 176 | Sout = self.copy() |
|
166 | 177 | Sout.merge(other) |
|
167 | 178 | return Sout |
|
168 | 179 | |
|
169 | 180 | def __sub__(self,other): |
|
170 | 181 | """Return S1-S2, where all keys in S2 have been deleted (if present) |
|
171 | 182 | from S1.""" |
|
172 | 183 | Sout = self.copy() |
|
173 | 184 | Sout -= other |
|
174 | 185 | return Sout |
|
175 | 186 | |
|
176 | 187 | def __isub__(self,other): |
|
177 | 188 | """Do in place S = S - S2, meaning all keys in S2 have been deleted |
|
178 | 189 | (if present) from S1.""" |
|
179 | 190 | |
|
180 | 191 | for k in other.keys(): |
|
181 | 192 | if self.has_key(k): |
|
182 | 193 | del self.__dict__[k] |
|
183 | 194 | |
|
184 | 195 | def __make_dict(self,__loc_data__,**kw): |
|
185 | 196 | "Helper function for update and merge. Return a dict from data." |
|
186 | 197 | |
|
187 | 198 | if __loc_data__ == None: |
|
188 | 199 | dict = {} |
|
189 | 200 | elif type(__loc_data__) is types.DictType: |
|
190 | 201 | dict = __loc_data__ |
|
191 | 202 | elif isinstance(__loc_data__,Struct): |
|
192 | 203 | dict = __loc_data__.__dict__ |
|
193 | 204 | else: |
|
194 | 205 | raise TypeError, 'Update with a dict, a Struct or key=val pairs.' |
|
195 | 206 | if kw: |
|
196 | 207 | dict.update(kw) |
|
197 | 208 | return dict |
|
198 | 209 | |
|
199 | 210 | def __dict_invert(self,dict): |
|
200 | 211 | """Helper function for merge. Takes a dictionary whose values are |
|
201 | 212 | lists and returns a dict. with the elements of each list as keys and |
|
202 | 213 | the original keys as values.""" |
|
203 | 214 | |
|
204 | 215 | outdict = {} |
|
205 | 216 | for k,lst in dict.items(): |
|
206 | 217 | if type(lst) is types.StringType: |
|
207 | 218 | lst = lst.split() |
|
208 | 219 | for entry in lst: |
|
209 | 220 | outdict[entry] = k |
|
210 | 221 | return outdict |
|
211 | 222 | |
|
212 | 223 | def clear(self): |
|
213 | 224 | """Clear all attributes.""" |
|
214 | 225 | self.__dict__.clear() |
|
215 | 226 | |
|
216 | 227 | def copy(self): |
|
217 | 228 | """Return a (shallow) copy of a Struct.""" |
|
218 | 229 | return Struct(self.__dict__.copy()) |
|
219 | 230 | |
|
220 | 231 | def dict(self): |
|
221 | 232 | """Return the Struct's dictionary.""" |
|
222 | 233 | return self.__dict__ |
|
223 | 234 | |
|
224 | 235 | def dictcopy(self): |
|
225 | 236 | """Return a (shallow) copy of the Struct's dictionary.""" |
|
226 | 237 | return self.__dict__.copy() |
|
227 | 238 | |
|
228 | 239 | def popitem(self): |
|
229 | 240 | """S.popitem() -> (k, v), remove and return some (key, value) pair as |
|
230 | 241 | a 2-tuple; but raise KeyError if S is empty.""" |
|
231 | 242 | return self.__dict__.popitem() |
|
232 | 243 | |
|
233 | 244 | def update(self,__loc_data__=None,**kw): |
|
234 | 245 | """Update (merge) with data from another Struct or from a dictionary. |
|
235 | 246 | Optionally, one or more key=value pairs can be given at the end for |
|
236 | 247 | direct update.""" |
|
237 | 248 | |
|
238 | 249 | # The funny name __loc_data__ is to prevent a common variable name which |
|
239 | 250 | # could be a fieled of a Struct to collide with this parameter. The problem |
|
240 | 251 | # would arise if the function is called with a keyword with this same name |
|
241 | 252 | # that a user means to add as a Struct field. |
|
242 | 253 | newdict = Struct.__make_dict(self,__loc_data__,**kw) |
|
243 | 254 | for k,v in newdict.items(): |
|
244 | 255 | self[k] = v |
|
245 | 256 | |
|
246 | 257 | def merge(self,__loc_data__=None,__conflict_solve=None,**kw): |
|
247 | 258 | """S.merge(data,conflict,k=v1,k=v2,...) -> merge data and k=v into S. |
|
248 | 259 | |
|
249 | 260 | This is similar to update(), but much more flexible. First, a dict is |
|
250 | 261 | made from data+key=value pairs. When merging this dict with the Struct |
|
251 | 262 | S, the optional dictionary 'conflict' is used to decide what to do. |
|
252 | 263 | |
|
253 | 264 | If conflict is not given, the default behavior is to preserve any keys |
|
254 | 265 | with their current value (the opposite of the update method's |
|
255 | 266 | behavior). |
|
256 | 267 | |
|
257 | 268 | conflict is a dictionary of binary functions which will be used to |
|
258 | 269 | solve key conflicts. It must have the following structure: |
|
259 | 270 | |
|
260 | 271 | conflict == { fn1 : [Skey1,Skey2,...], fn2 : [Skey3], etc } |
|
261 | 272 | |
|
262 | 273 | Values must be lists or whitespace separated strings which are |
|
263 | 274 | automatically converted to lists of strings by calling string.split(). |
|
264 | 275 | |
|
265 | 276 | Each key of conflict is a function which defines a policy for |
|
266 | 277 | resolving conflicts when merging with the input data. Each fn must be |
|
267 | 278 | a binary function which returns the desired outcome for a key |
|
268 | 279 | conflict. These functions will be called as fn(old,new). |
|
269 | 280 | |
|
270 | 281 | An example is probably in order. Suppose you are merging the struct S |
|
271 | 282 | with a dict D and the following conflict policy dict: |
|
272 | 283 | |
|
273 | 284 | S.merge(D,{fn1:['a','b',4], fn2:'key_c key_d'}) |
|
274 | 285 | |
|
275 | 286 | If the key 'a' is found in both S and D, the merge method will call: |
|
276 | 287 | |
|
277 | 288 | S['a'] = fn1(S['a'],D['a']) |
|
278 | 289 | |
|
279 | 290 | As a convenience, merge() provides five (the most commonly needed) |
|
280 | 291 | pre-defined policies: preserve, update, add, add_flip and add_s. The |
|
281 | 292 | easiest explanation is their implementation: |
|
282 | 293 | |
|
283 | 294 | preserve = lambda old,new: old |
|
284 | 295 | update = lambda old,new: new |
|
285 | 296 | add = lambda old,new: old + new |
|
286 | 297 | add_flip = lambda old,new: new + old # note change of order! |
|
287 | 298 | add_s = lambda old,new: old + ' ' + new # only works for strings! |
|
288 | 299 | |
|
289 | 300 | You can use those four words (as strings) as keys in conflict instead |
|
290 | 301 | of defining them as functions, and the merge method will substitute |
|
291 | 302 | the appropriate functions for you. That is, the call |
|
292 | 303 | |
|
293 | 304 | S.merge(D,{'preserve':'a b c','add':[4,5,'d'],my_function:[6]}) |
|
294 | 305 | |
|
295 | 306 | will automatically substitute the functions preserve and add for the |
|
296 | 307 | names 'preserve' and 'add' before making any function calls. |
|
297 | 308 | |
|
298 | 309 | For more complicated conflict resolution policies, you still need to |
|
299 | 310 | construct your own functions. """ |
|
300 | 311 | |
|
301 | 312 | data_dict = Struct.__make_dict(self,__loc_data__,**kw) |
|
302 | 313 | |
|
303 | 314 | # policies for conflict resolution: two argument functions which return |
|
304 | 315 | # the value that will go in the new struct |
|
305 | 316 | preserve = lambda old,new: old |
|
306 | 317 | update = lambda old,new: new |
|
307 | 318 | add = lambda old,new: old + new |
|
308 | 319 | add_flip = lambda old,new: new + old # note change of order! |
|
309 | 320 | add_s = lambda old,new: old + ' ' + new |
|
310 | 321 | |
|
311 | 322 | # default policy is to keep current keys when there's a conflict |
|
312 | 323 | conflict_solve = list2dict2(self.keys(),default = preserve) |
|
313 | 324 | |
|
314 | 325 | # the conflict_solve dictionary is given by the user 'inverted': we |
|
315 | 326 | # need a name-function mapping, it comes as a function -> names |
|
316 | 327 | # dict. Make a local copy (b/c we'll make changes), replace user |
|
317 | 328 | # strings for the three builtin policies and invert it. |
|
318 | 329 | if __conflict_solve: |
|
319 | 330 | inv_conflict_solve_user = __conflict_solve.copy() |
|
320 | 331 | for name, func in [('preserve',preserve), ('update',update), |
|
321 |
('add',add), ('add_flip',add_flip), |
|
|
332 | ('add',add), ('add_flip',add_flip), | |
|
333 | ('add_s',add_s)]: | |
|
322 | 334 | if name in inv_conflict_solve_user.keys(): |
|
323 | 335 | inv_conflict_solve_user[func] = inv_conflict_solve_user[name] |
|
324 | 336 | del inv_conflict_solve_user[name] |
|
325 | 337 | conflict_solve.update(Struct.__dict_invert(self,inv_conflict_solve_user)) |
|
326 | 338 | #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg |
|
327 | 339 | #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve) |
|
328 | 340 | for key in data_dict: |
|
329 | 341 | if key not in self: |
|
330 | 342 | self[key] = data_dict[key] |
|
331 | 343 | else: |
|
332 | 344 | self[key] = conflict_solve[key](self[key],data_dict[key]) |
|
333 | 345 | |
|
334 | 346 | def has_key(self,key): |
|
335 | 347 | """Like has_key() dictionary method.""" |
|
336 | 348 | return self.__dict__.has_key(key) |
|
337 | 349 | |
|
338 | 350 | def hasattr(self,key): |
|
339 | 351 | """hasattr function available as a method. |
|
340 | 352 | |
|
341 | 353 | Implemented like has_key, to make sure that all available keys in the |
|
342 | 354 | internal dictionary of the Struct appear also as attributes (even |
|
343 | 355 | numeric keys).""" |
|
344 | 356 | return self.__dict__.has_key(key) |
|
345 | 357 | |
|
346 | 358 | def items(self): |
|
347 | 359 | """Return the items in the Struct's dictionary, in the same format |
|
348 | 360 | as a call to {}.items().""" |
|
349 | 361 | return self.__dict__.items() |
|
350 | 362 | |
|
351 | 363 | def keys(self): |
|
352 | 364 | """Return the keys in the Struct's dictionary, in the same format |
|
353 | 365 | as a call to {}.keys().""" |
|
354 | 366 | return self.__dict__.keys() |
|
355 | 367 | |
|
356 | 368 | def values(self,keys=None): |
|
357 | 369 | """Return the values in the Struct's dictionary, in the same format |
|
358 | 370 | as a call to {}.values(). |
|
359 | 371 | |
|
360 | 372 | Can be called with an optional argument keys, which must be a list or |
|
361 | 373 | tuple of keys. In this case it returns only the values corresponding |
|
362 | 374 | to those keys (allowing a form of 'slicing' for Structs).""" |
|
363 | 375 | if not keys: |
|
364 | 376 | return self.__dict__.values() |
|
365 | 377 | else: |
|
366 | 378 | ret=[] |
|
367 | 379 | for k in keys: |
|
368 | 380 | ret.append(self[k]) |
|
369 | 381 | return ret |
|
370 | 382 | |
|
371 | 383 | def get(self,attr,val=None): |
|
372 |
"""S.get(k[,d]) -> S[k] if |
|
|
384 | """S.get(k[,d]) -> S[k] if k in S, else d. d defaults to None.""" | |
|
373 | 385 | try: |
|
374 | 386 | return self[attr] |
|
375 | 387 | except KeyError: |
|
376 | 388 | return val |
|
377 | 389 | |
|
378 | 390 | def setdefault(self,attr,val=None): |
|
379 |
"""S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if not S |
|
|
391 | """S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if k not in S""" | |
|
380 | 392 | if not self.has_key(attr): |
|
381 | 393 | self[attr] = val |
|
382 | 394 | return self.get(attr,val) |
|
383 | 395 | |
|
384 | 396 | def allow_new_attr(self, allow = True): |
|
385 | 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 |
|
|
388 | change already exists in this Struct. | |
|
399 | This can be used to catch typos by verifying that the attribute user | |
|
400 | tries to change already exists in this Struct. | |
|
389 | 401 | """ |
|
390 | 402 | self['__allownew'] = allow |
|
391 | 403 | |
|
392 | 404 | |
|
393 | 405 | # end class Struct |
|
394 | 406 |
@@ -1,874 +1,876 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | # -*- test-case-name: IPython.kernel.tests.test_engineservice -*- |
|
3 | 3 | |
|
4 | 4 | """A Twisted Service Representation of the IPython core. |
|
5 | 5 | |
|
6 | 6 | The IPython Core exposed to the network is called the Engine. Its |
|
7 | 7 | representation in Twisted in the EngineService. Interfaces and adapters |
|
8 | 8 | are used to abstract out the details of the actual network protocol used. |
|
9 | 9 | The EngineService is an Engine that knows nothing about the actual protocol |
|
10 | 10 | used. |
|
11 | 11 | |
|
12 | 12 | The EngineService is exposed with various network protocols in modules like: |
|
13 | 13 | |
|
14 | 14 | enginepb.py |
|
15 | 15 | enginevanilla.py |
|
16 | 16 | |
|
17 | 17 | As of 12/12/06 the classes in this module have been simplified greatly. It was |
|
18 | 18 | felt that we had over-engineered things. To improve the maintainability of the |
|
19 | 19 | code we have taken out the ICompleteEngine interface and the completeEngine |
|
20 | 20 | method that automatically added methods to engines. |
|
21 | 21 | |
|
22 | 22 | """ |
|
23 | 23 | |
|
24 | 24 | __docformat__ = "restructuredtext en" |
|
25 | 25 | |
|
26 | 26 | #------------------------------------------------------------------------------- |
|
27 | 27 | # Copyright (C) 2008 The IPython Development Team |
|
28 | 28 | # |
|
29 | 29 | # Distributed under the terms of the BSD License. The full license is in |
|
30 | 30 | # the file COPYING, distributed as part of this software. |
|
31 | 31 | #------------------------------------------------------------------------------- |
|
32 | 32 | |
|
33 | 33 | #------------------------------------------------------------------------------- |
|
34 | 34 | # Imports |
|
35 | 35 | #------------------------------------------------------------------------------- |
|
36 | 36 | |
|
37 | 37 | import os, sys, copy |
|
38 | 38 | import cPickle as pickle |
|
39 | 39 | from new import instancemethod |
|
40 | 40 | |
|
41 | 41 | from twisted.application import service |
|
42 | 42 | from twisted.internet import defer, reactor |
|
43 | 43 | from twisted.python import log, failure, components |
|
44 | 44 | import zope.interface as zi |
|
45 | 45 | |
|
46 | 46 | from IPython.kernel.core.interpreter import Interpreter |
|
47 | 47 | from IPython.kernel import newserialized, error, util |
|
48 | 48 | from IPython.kernel.util import printer |
|
49 | 49 | from IPython.kernel.twistedutil import gatherBoth, DeferredList |
|
50 | 50 | from IPython.kernel import codeutil |
|
51 | 51 | |
|
52 | 52 | |
|
53 | 53 | #------------------------------------------------------------------------------- |
|
54 | 54 | # Interface specification for the Engine |
|
55 | 55 | #------------------------------------------------------------------------------- |
|
56 | 56 | |
|
57 | 57 | class IEngineCore(zi.Interface): |
|
58 | 58 | """The minimal required interface for the IPython Engine. |
|
59 | 59 | |
|
60 | 60 | This interface provides a formal specification of the IPython core. |
|
61 | 61 | All these methods should return deferreds regardless of what side of a |
|
62 | 62 | network connection they are on. |
|
63 | 63 | |
|
64 | 64 | In general, this class simply wraps a shell class and wraps its return |
|
65 | 65 | values as Deferred objects. If the underlying shell class method raises |
|
66 | 66 | an exception, this class should convert it to a twisted.failure.Failure |
|
67 | 67 | that will be propagated along the Deferred's errback chain. |
|
68 | 68 | |
|
69 | 69 | In addition, Failures are aggressive. By this, we mean that if a method |
|
70 | 70 | is performing multiple actions (like pulling multiple object) if any |
|
71 | 71 | single one fails, the entire method will fail with that Failure. It is |
|
72 | 72 | all or nothing. |
|
73 | 73 | """ |
|
74 | 74 | |
|
75 | 75 | id = zi.interface.Attribute("the id of the Engine object") |
|
76 | 76 | properties = zi.interface.Attribute("A dict of properties of the Engine") |
|
77 | 77 | |
|
78 | 78 | def execute(lines): |
|
79 | 79 | """Execute lines of Python code. |
|
80 | 80 | |
|
81 | 81 | Returns a dictionary with keys (id, number, stdin, stdout, stderr) |
|
82 | 82 | upon success. |
|
83 | 83 | |
|
84 | 84 | Returns a failure object if the execution of lines raises an exception. |
|
85 | 85 | """ |
|
86 | 86 | |
|
87 | 87 | def push(namespace): |
|
88 | 88 | """Push dict namespace into the user's namespace. |
|
89 | 89 | |
|
90 | 90 | Returns a deferred to None or a failure. |
|
91 | 91 | """ |
|
92 | 92 | |
|
93 | 93 | def pull(keys): |
|
94 | 94 | """Pulls values out of the user's namespace by keys. |
|
95 | 95 | |
|
96 | 96 | Returns a deferred to a tuple objects or a single object. |
|
97 | 97 | |
|
98 | 98 | Raises NameError if any one of objects doess not exist. |
|
99 | 99 | """ |
|
100 | 100 | |
|
101 | 101 | def push_function(namespace): |
|
102 | 102 | """Push a dict of key, function pairs into the user's namespace. |
|
103 | 103 | |
|
104 | 104 | Returns a deferred to None or a failure.""" |
|
105 | 105 | |
|
106 | 106 | def pull_function(keys): |
|
107 | 107 | """Pulls functions out of the user's namespace by keys. |
|
108 | 108 | |
|
109 | 109 | Returns a deferred to a tuple of functions or a single function. |
|
110 | 110 | |
|
111 | 111 | Raises NameError if any one of the functions does not exist. |
|
112 | 112 | """ |
|
113 | 113 | |
|
114 | 114 | def get_result(i=None): |
|
115 | 115 | """Get the stdin/stdout/stderr of command i. |
|
116 | 116 | |
|
117 | 117 | Returns a deferred to a dict with keys |
|
118 | 118 | (id, number, stdin, stdout, stderr). |
|
119 | 119 | |
|
120 | 120 | Raises IndexError if command i does not exist. |
|
121 | 121 | Raises TypeError if i in not an int. |
|
122 | 122 | """ |
|
123 | 123 | |
|
124 | 124 | def reset(): |
|
125 | 125 | """Reset the shell. |
|
126 | 126 | |
|
127 | 127 | This clears the users namespace. Won't cause modules to be |
|
128 | 128 | reloaded. Should also re-initialize certain variables like id. |
|
129 | 129 | """ |
|
130 | 130 | |
|
131 | 131 | def kill(): |
|
132 | 132 | """Kill the engine by stopping the reactor.""" |
|
133 | 133 | |
|
134 | 134 | def keys(): |
|
135 | 135 | """Return the top level variables in the users namspace. |
|
136 | 136 | |
|
137 | 137 | Returns a deferred to a dict.""" |
|
138 | 138 | |
|
139 | 139 | |
|
140 | 140 | class IEngineSerialized(zi.Interface): |
|
141 | 141 | """Push/Pull methods that take Serialized objects. |
|
142 | 142 | |
|
143 | 143 | All methods should return deferreds. |
|
144 | 144 | """ |
|
145 | 145 | |
|
146 | 146 | def push_serialized(namespace): |
|
147 | 147 | """Push a dict of keys and Serialized objects into the user's namespace.""" |
|
148 | 148 | |
|
149 | 149 | def pull_serialized(keys): |
|
150 | 150 | """Pull objects by key from the user's namespace as Serialized. |
|
151 | 151 | |
|
152 | 152 | Returns a list of or one Serialized. |
|
153 | 153 | |
|
154 | 154 | Raises NameError is any one of the objects does not exist. |
|
155 | 155 | """ |
|
156 | 156 | |
|
157 | 157 | |
|
158 | 158 | class IEngineProperties(zi.Interface): |
|
159 | 159 | """Methods for access to the properties object of an Engine""" |
|
160 | 160 | |
|
161 | 161 | properties = zi.Attribute("A StrictDict object, containing the properties") |
|
162 | 162 | |
|
163 | 163 | def set_properties(properties): |
|
164 | 164 | """set properties by key and value""" |
|
165 | 165 | |
|
166 | 166 | def get_properties(keys=None): |
|
167 | 167 | """get a list of properties by `keys`, if no keys specified, get all""" |
|
168 | 168 | |
|
169 | 169 | def del_properties(keys): |
|
170 | 170 | """delete properties by `keys`""" |
|
171 | 171 | |
|
172 | 172 | def has_properties(keys): |
|
173 | 173 | """get a list of bool values for whether `properties` has `keys`""" |
|
174 | 174 | |
|
175 | 175 | def clear_properties(): |
|
176 | 176 | """clear the properties dict""" |
|
177 | 177 | |
|
178 | 178 | class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties): |
|
179 | 179 | """The basic engine interface that EngineService will implement. |
|
180 | 180 | |
|
181 | 181 | This exists so it is easy to specify adapters that adapt to and from the |
|
182 | 182 | API that the basic EngineService implements. |
|
183 | 183 | """ |
|
184 | 184 | pass |
|
185 | 185 | |
|
186 | 186 | class IEngineQueued(IEngineBase): |
|
187 | 187 | """Interface for adding a queue to an IEngineBase. |
|
188 | 188 | |
|
189 | 189 | This interface extends the IEngineBase interface to add methods for managing |
|
190 | 190 | the engine's queue. The implicit details of this interface are that the |
|
191 | 191 | execution of all methods declared in IEngineBase should appropriately be |
|
192 | 192 | put through a queue before execution. |
|
193 | 193 | |
|
194 | 194 | All methods should return deferreds. |
|
195 | 195 | """ |
|
196 | 196 | |
|
197 | 197 | def clear_queue(): |
|
198 | 198 | """Clear the queue.""" |
|
199 | 199 | |
|
200 | 200 | def queue_status(): |
|
201 | 201 | """Get the queued and pending commands in the queue.""" |
|
202 | 202 | |
|
203 | 203 | def register_failure_observer(obs): |
|
204 | 204 | """Register an observer of pending Failures. |
|
205 | 205 | |
|
206 | 206 | The observer must implement IFailureObserver. |
|
207 | 207 | """ |
|
208 | 208 | |
|
209 | 209 | def unregister_failure_observer(obs): |
|
210 | 210 | """Unregister an observer of pending Failures.""" |
|
211 | 211 | |
|
212 | 212 | |
|
213 | 213 | class IEngineThreaded(zi.Interface): |
|
214 | 214 | """A place holder for threaded commands. |
|
215 | 215 | |
|
216 | 216 | All methods should return deferreds. |
|
217 | 217 | """ |
|
218 | 218 | pass |
|
219 | 219 | |
|
220 | 220 | |
|
221 | 221 | #------------------------------------------------------------------------------- |
|
222 | 222 | # Functions and classes to implement the EngineService |
|
223 | 223 | #------------------------------------------------------------------------------- |
|
224 | 224 | |
|
225 | 225 | |
|
226 | 226 | class StrictDict(dict): |
|
227 | 227 | """This is a strict copying dictionary for use as the interface to the |
|
228 | 228 | properties of an Engine. |
|
229 | 229 | :IMPORTANT: |
|
230 | 230 | This object copies the values you set to it, and returns copies to you |
|
231 | 231 | when you request them. The only way to change properties os explicitly |
|
232 | 232 | through the setitem and getitem of the dictionary interface. |
|
233 | 233 | Example: |
|
234 | 234 | >>> e = kernel.get_engine(id) |
|
235 | 235 | >>> L = someList |
|
236 | 236 | >>> e.properties['L'] = L |
|
237 | 237 | >>> L == e.properties['L'] |
|
238 | 238 | ... True |
|
239 | 239 | >>> L.append(something Else) |
|
240 | 240 | >>> L == e.properties['L'] |
|
241 | 241 | ... False |
|
242 | 242 | |
|
243 | 243 | getitem copies, so calls to methods of objects do not affect the |
|
244 | 244 | properties, as in the following example: |
|
245 | 245 | >>> e.properties[1] = range(2) |
|
246 | 246 | >>> print e.properties[1] |
|
247 | 247 | ... [0, 1] |
|
248 | 248 | >>> e.properties[1].append(2) |
|
249 | 249 | >>> print e.properties[1] |
|
250 | 250 | ... [0, 1] |
|
251 | 251 | |
|
252 | 252 | """ |
|
253 | 253 | def __init__(self, *args, **kwargs): |
|
254 | 254 | dict.__init__(self, *args, **kwargs) |
|
255 | 255 | self.modified = True |
|
256 | 256 | |
|
257 | 257 | def __getitem__(self, key): |
|
258 | 258 | return copy.deepcopy(dict.__getitem__(self, key)) |
|
259 | 259 | |
|
260 | 260 | def __setitem__(self, key, value): |
|
261 | 261 | # check if this entry is valid for transport around the network |
|
262 | 262 | # and copying |
|
263 | 263 | try: |
|
264 | 264 | pickle.dumps(key, 2) |
|
265 | 265 | pickle.dumps(value, 2) |
|
266 | 266 | newvalue = copy.deepcopy(value) |
|
267 | 267 | except: |
|
268 | 268 | raise error.InvalidProperty(value) |
|
269 | 269 | dict.__setitem__(self, key, newvalue) |
|
270 | 270 | self.modified = True |
|
271 | 271 | |
|
272 | 272 | def __delitem__(self, key): |
|
273 | 273 | dict.__delitem__(self, key) |
|
274 | 274 | self.modified = True |
|
275 | 275 | |
|
276 | 276 | def update(self, dikt): |
|
277 | 277 | for k,v in dikt.iteritems(): |
|
278 | 278 | self[k] = v |
|
279 | 279 | |
|
280 | 280 | def pop(self, key): |
|
281 | 281 | self.modified = True |
|
282 | 282 | return dict.pop(self, key) |
|
283 | 283 | |
|
284 | 284 | def popitem(self): |
|
285 | 285 | self.modified = True |
|
286 | 286 | return dict.popitem(self) |
|
287 | 287 | |
|
288 | 288 | def clear(self): |
|
289 | 289 | self.modified = True |
|
290 | 290 | dict.clear(self) |
|
291 | 291 | |
|
292 | 292 | def subDict(self, *keys): |
|
293 | 293 | d = {} |
|
294 | 294 | for key in keys: |
|
295 | 295 | d[key] = self[key] |
|
296 | 296 | return d |
|
297 | 297 | |
|
298 | 298 | |
|
299 | 299 | |
|
300 | 300 | class EngineAPI(object): |
|
301 | 301 | """This is the object through which the user can edit the `properties` |
|
302 | 302 | attribute of an Engine. |
|
303 | 303 | The Engine Properties object copies all object in and out of itself. |
|
304 | 304 | See the EngineProperties object for details. |
|
305 | 305 | """ |
|
306 | 306 | _fix=False |
|
307 | 307 | def __init__(self, id): |
|
308 | 308 | self.id = id |
|
309 | 309 | self.properties = StrictDict() |
|
310 | 310 | self._fix=True |
|
311 | 311 | |
|
312 | 312 | def __setattr__(self, k,v): |
|
313 | 313 | if self._fix: |
|
314 | 314 | raise error.KernelError("I am protected!") |
|
315 | 315 | else: |
|
316 | 316 | object.__setattr__(self, k, v) |
|
317 | 317 | |
|
318 | 318 | def __delattr__(self, key): |
|
319 | 319 | raise error.KernelError("I am protected!") |
|
320 | 320 | |
|
321 | 321 | |
|
322 | 322 | _apiDict = {} |
|
323 | 323 | |
|
324 | 324 | def get_engine(id): |
|
325 | 325 | """Get the Engine API object, whcih currently just provides the properties |
|
326 | 326 | object, by ID""" |
|
327 | 327 | global _apiDict |
|
328 | 328 | if not _apiDict.get(id): |
|
329 | 329 | _apiDict[id] = EngineAPI(id) |
|
330 | 330 | return _apiDict[id] |
|
331 | 331 | |
|
332 | 332 | def drop_engine(id): |
|
333 | 333 | """remove an engine""" |
|
334 | 334 | global _apiDict |
|
335 | 335 | if _apiDict.has_key(id): |
|
336 | 336 | del _apiDict[id] |
|
337 | 337 | |
|
338 | 338 | class EngineService(object, service.Service): |
|
339 | 339 | """Adapt a IPython shell into a IEngine implementing Twisted Service.""" |
|
340 | 340 | |
|
341 | 341 | zi.implements(IEngineBase) |
|
342 | 342 | name = 'EngineService' |
|
343 | 343 | |
|
344 | 344 | def __init__(self, shellClass=Interpreter, mpi=None): |
|
345 | 345 | """Create an EngineService. |
|
346 | 346 | |
|
347 | 347 | shellClass: something that implements IInterpreter or core1 |
|
348 | 348 | mpi: an mpi module that has rank and size attributes |
|
349 | 349 | """ |
|
350 | 350 | self.shellClass = shellClass |
|
351 | 351 | self.shell = self.shellClass() |
|
352 | 352 | self.mpi = mpi |
|
353 | 353 | self.id = None |
|
354 | 354 | self.properties = get_engine(self.id).properties |
|
355 | 355 | if self.mpi is not None: |
|
356 | 356 | log.msg("MPI started with rank = %i and size = %i" % |
|
357 | 357 | (self.mpi.rank, self.mpi.size)) |
|
358 | 358 | self.id = self.mpi.rank |
|
359 | 359 | self._seedNamespace() |
|
360 | 360 | |
|
361 | 361 | # Make id a property so that the shell can get the updated id |
|
362 | 362 | |
|
363 | 363 | def _setID(self, id): |
|
364 | 364 | self._id = id |
|
365 | 365 | self.properties = get_engine(id).properties |
|
366 | 366 | self.shell.push({'id': id}) |
|
367 | 367 | |
|
368 | 368 | def _getID(self): |
|
369 | 369 | return self._id |
|
370 | 370 | |
|
371 | 371 | id = property(_getID, _setID) |
|
372 | 372 | |
|
373 | 373 | def _seedNamespace(self): |
|
374 | 374 | self.shell.push({'mpi': self.mpi, 'id' : self.id}) |
|
375 | 375 | |
|
376 | 376 | def executeAndRaise(self, msg, callable, *args, **kwargs): |
|
377 | 377 | """Call a method of self.shell and wrap any exception.""" |
|
378 | 378 | d = defer.Deferred() |
|
379 | 379 | try: |
|
380 | 380 | result = callable(*args, **kwargs) |
|
381 | 381 | except: |
|
382 | 382 | # This gives the following: |
|
383 | 383 | # et=exception class |
|
384 | 384 | # ev=exception class instance |
|
385 | 385 | # tb=traceback object |
|
386 | 386 | et,ev,tb = sys.exc_info() |
|
387 | 387 | # This call adds attributes to the exception value |
|
388 | 388 | et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg) |
|
389 | 389 | # Add another attribute |
|
390 | 390 | ev._ipython_engine_info = msg |
|
391 | 391 | f = failure.Failure(ev,et,None) |
|
392 | 392 | d.errback(f) |
|
393 | 393 | else: |
|
394 | 394 | d.callback(result) |
|
395 | 395 | |
|
396 | 396 | return d |
|
397 | 397 | |
|
398 | 398 | # The IEngine methods. See the interface for documentation. |
|
399 | 399 | |
|
400 | 400 | def execute(self, lines): |
|
401 | 401 | msg = {'engineid':self.id, |
|
402 | 402 | 'method':'execute', |
|
403 | 403 | 'args':[lines]} |
|
404 | 404 | d = self.executeAndRaise(msg, self.shell.execute, lines) |
|
405 | 405 | d.addCallback(self.addIDToResult) |
|
406 | 406 | return d |
|
407 | 407 | |
|
408 | 408 | def addIDToResult(self, result): |
|
409 | 409 | result['id'] = self.id |
|
410 | 410 | return result |
|
411 | 411 | |
|
412 | 412 | def push(self, namespace): |
|
413 | 413 | msg = {'engineid':self.id, |
|
414 | 414 | 'method':'push', |
|
415 | 415 | 'args':[repr(namespace.keys())]} |
|
416 | 416 | d = self.executeAndRaise(msg, self.shell.push, namespace) |
|
417 | 417 | return d |
|
418 | 418 | |
|
419 | 419 | def pull(self, keys): |
|
420 | 420 | msg = {'engineid':self.id, |
|
421 | 421 | 'method':'pull', |
|
422 | 422 | 'args':[repr(keys)]} |
|
423 | 423 | d = self.executeAndRaise(msg, self.shell.pull, keys) |
|
424 | 424 | return d |
|
425 | 425 | |
|
426 | 426 | def push_function(self, namespace): |
|
427 | 427 | msg = {'engineid':self.id, |
|
428 | 428 | 'method':'push_function', |
|
429 | 429 | 'args':[repr(namespace.keys())]} |
|
430 | 430 | d = self.executeAndRaise(msg, self.shell.push_function, namespace) |
|
431 | 431 | return d |
|
432 | 432 | |
|
433 | 433 | def pull_function(self, keys): |
|
434 | 434 | msg = {'engineid':self.id, |
|
435 | 435 | 'method':'pull_function', |
|
436 | 436 | 'args':[repr(keys)]} |
|
437 | 437 | d = self.executeAndRaise(msg, self.shell.pull_function, keys) |
|
438 | 438 | return d |
|
439 | 439 | |
|
440 | 440 | def get_result(self, i=None): |
|
441 | 441 | msg = {'engineid':self.id, |
|
442 | 442 | 'method':'get_result', |
|
443 | 443 | 'args':[repr(i)]} |
|
444 | 444 | d = self.executeAndRaise(msg, self.shell.getCommand, i) |
|
445 | 445 | d.addCallback(self.addIDToResult) |
|
446 | 446 | return d |
|
447 | 447 | |
|
448 | 448 | def reset(self): |
|
449 | 449 | msg = {'engineid':self.id, |
|
450 | 450 | 'method':'reset', |
|
451 | 451 | 'args':[]} |
|
452 | 452 | del self.shell |
|
453 | 453 | self.shell = self.shellClass() |
|
454 | 454 | self.properties.clear() |
|
455 | 455 | d = self.executeAndRaise(msg, self._seedNamespace) |
|
456 | 456 | return d |
|
457 | 457 | |
|
458 | 458 | def kill(self): |
|
459 | 459 | drop_engine(self.id) |
|
460 | 460 | try: |
|
461 | 461 | reactor.stop() |
|
462 | 462 | except RuntimeError: |
|
463 | 463 | log.msg('The reactor was not running apparently.') |
|
464 | 464 | return defer.fail() |
|
465 | 465 | else: |
|
466 | 466 | return defer.succeed(None) |
|
467 | 467 | |
|
468 | 468 | def keys(self): |
|
469 | 469 | """Return a list of variables names in the users top level namespace. |
|
470 | 470 | |
|
471 | 471 | This used to return a dict of all the keys/repr(values) in the |
|
472 | 472 | user's namespace. This was too much info for the ControllerService |
|
473 | 473 | to handle so it is now just a list of keys. |
|
474 | 474 | """ |
|
475 | 475 | |
|
476 | 476 | remotes = [] |
|
477 | 477 | for k in self.shell.user_ns.iterkeys(): |
|
478 | 478 | if k not in ['__name__', '_ih', '_oh', '__builtins__', |
|
479 | 479 | 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']: |
|
480 | 480 | remotes.append(k) |
|
481 | 481 | return defer.succeed(remotes) |
|
482 | 482 | |
|
483 | 483 | def set_properties(self, properties): |
|
484 | 484 | msg = {'engineid':self.id, |
|
485 | 485 | 'method':'set_properties', |
|
486 | 486 | 'args':[repr(properties.keys())]} |
|
487 | 487 | return self.executeAndRaise(msg, self.properties.update, properties) |
|
488 | 488 | |
|
489 | 489 | def get_properties(self, keys=None): |
|
490 | 490 | msg = {'engineid':self.id, |
|
491 | 491 | 'method':'get_properties', |
|
492 | 492 | 'args':[repr(keys)]} |
|
493 | 493 | if keys is None: |
|
494 | 494 | keys = self.properties.keys() |
|
495 | 495 | return self.executeAndRaise(msg, self.properties.subDict, *keys) |
|
496 | 496 | |
|
497 | 497 | def _doDel(self, keys): |
|
498 | 498 | for key in keys: |
|
499 | 499 | del self.properties[key] |
|
500 | 500 | |
|
501 | 501 | def del_properties(self, keys): |
|
502 | 502 | msg = {'engineid':self.id, |
|
503 | 503 | 'method':'del_properties', |
|
504 | 504 | 'args':[repr(keys)]} |
|
505 | 505 | return self.executeAndRaise(msg, self._doDel, keys) |
|
506 | 506 | |
|
507 | 507 | def _doHas(self, keys): |
|
508 | 508 | return [self.properties.has_key(key) for key in keys] |
|
509 | 509 | |
|
510 | 510 | def has_properties(self, keys): |
|
511 | 511 | msg = {'engineid':self.id, |
|
512 | 512 | 'method':'has_properties', |
|
513 | 513 | 'args':[repr(keys)]} |
|
514 | 514 | return self.executeAndRaise(msg, self._doHas, keys) |
|
515 | 515 | |
|
516 | 516 | def clear_properties(self): |
|
517 | 517 | msg = {'engineid':self.id, |
|
518 | 518 | 'method':'clear_properties', |
|
519 | 519 | 'args':[]} |
|
520 | 520 | return self.executeAndRaise(msg, self.properties.clear) |
|
521 | 521 | |
|
522 | 522 | def push_serialized(self, sNamespace): |
|
523 | 523 | msg = {'engineid':self.id, |
|
524 | 524 | 'method':'push_serialized', |
|
525 | 525 | 'args':[repr(sNamespace.keys())]} |
|
526 | 526 | ns = {} |
|
527 | 527 | for k,v in sNamespace.iteritems(): |
|
528 | 528 | try: |
|
529 | 529 | unserialized = newserialized.IUnSerialized(v) |
|
530 | 530 | ns[k] = unserialized.getObject() |
|
531 | 531 | except: |
|
532 | 532 | return defer.fail() |
|
533 | 533 | return self.executeAndRaise(msg, self.shell.push, ns) |
|
534 | 534 | |
|
535 | 535 | def pull_serialized(self, keys): |
|
536 | 536 | msg = {'engineid':self.id, |
|
537 | 537 | 'method':'pull_serialized', |
|
538 | 538 | 'args':[repr(keys)]} |
|
539 | 539 | if isinstance(keys, str): |
|
540 | 540 | keys = [keys] |
|
541 | 541 | if len(keys)==1: |
|
542 | 542 | d = self.executeAndRaise(msg, self.shell.pull, keys) |
|
543 | 543 | d.addCallback(newserialized.serialize) |
|
544 | 544 | return d |
|
545 | 545 | elif len(keys)>1: |
|
546 | 546 | d = self.executeAndRaise(msg, self.shell.pull, keys) |
|
547 | 547 | @d.addCallback |
|
548 | 548 | def packThemUp(values): |
|
549 | 549 | serials = [] |
|
550 | 550 | for v in values: |
|
551 | 551 | try: |
|
552 | 552 | serials.append(newserialized.serialize(v)) |
|
553 | 553 | except: |
|
554 | 554 | return defer.fail(failure.Failure()) |
|
555 | 555 | return serials |
|
556 | 556 | return packThemUp |
|
557 | 557 | |
|
558 | 558 | |
|
559 | 559 | def queue(methodToQueue): |
|
560 | 560 | def queuedMethod(this, *args, **kwargs): |
|
561 | 561 | name = methodToQueue.__name__ |
|
562 | 562 | return this.submitCommand(Command(name, *args, **kwargs)) |
|
563 | 563 | return queuedMethod |
|
564 | 564 | |
|
565 | 565 | class QueuedEngine(object): |
|
566 | 566 | """Adapt an IEngineBase to an IEngineQueued by wrapping it. |
|
567 | 567 | |
|
568 | 568 | The resulting object will implement IEngineQueued which extends |
|
569 | 569 | IEngineCore which extends (IEngineBase, IEngineSerialized). |
|
570 | 570 | |
|
571 | 571 | This seems like the best way of handling it, but I am not sure. The |
|
572 | 572 | other option is to have the various base interfaces be used like |
|
573 | 573 | mix-in intefaces. The problem I have with this is adpatation is |
|
574 | 574 | more difficult and complicated because there can be can multiple |
|
575 | 575 | original and final Interfaces. |
|
576 | 576 | """ |
|
577 | 577 | |
|
578 | 578 | zi.implements(IEngineQueued) |
|
579 | 579 | |
|
580 | 580 | def __init__(self, engine): |
|
581 | 581 | """Create a QueuedEngine object from an engine |
|
582 | 582 | |
|
583 | 583 | engine: An implementor of IEngineCore and IEngineSerialized |
|
584 | 584 | keepUpToDate: whether to update the remote status when the |
|
585 | 585 | queue is empty. Defaults to False. |
|
586 | 586 | """ |
|
587 | 587 | |
|
588 | 588 | # This is the right way to do these tests rather than |
|
589 | 589 | # IEngineCore in list(zi.providedBy(engine)) which will only |
|
590 | 590 | # picks of the interfaces that are directly declared by engine. |
|
591 | 591 | assert IEngineBase.providedBy(engine), \ |
|
592 | 592 | "engine passed to QueuedEngine doesn't provide IEngineBase" |
|
593 | 593 | |
|
594 | 594 | self.engine = engine |
|
595 | 595 | self.id = engine.id |
|
596 | 596 | self.queued = [] |
|
597 | 597 | self.history = {} |
|
598 | 598 | self.engineStatus = {} |
|
599 | 599 | self.currentCommand = None |
|
600 | 600 | self.failureObservers = [] |
|
601 | 601 | |
|
602 | 602 | def _get_properties(self): |
|
603 | 603 | return self.engine.properties |
|
604 | 604 | |
|
605 | 605 | properties = property(_get_properties, lambda self, _: None) |
|
606 | 606 | # Queue management methods. You should not call these directly |
|
607 | 607 | |
|
608 | 608 | def submitCommand(self, cmd): |
|
609 | 609 | """Submit command to queue.""" |
|
610 | 610 | |
|
611 | 611 | d = defer.Deferred() |
|
612 | 612 | cmd.setDeferred(d) |
|
613 | 613 | if self.currentCommand is not None: |
|
614 | 614 | if self.currentCommand.finished: |
|
615 | 615 | # log.msg("Running command immediately: %r" % cmd) |
|
616 | 616 | self.currentCommand = cmd |
|
617 | 617 | self.runCurrentCommand() |
|
618 | 618 | else: # command is still running |
|
619 | 619 | # log.msg("Command is running: %r" % self.currentCommand) |
|
620 | 620 | # log.msg("Queueing: %r" % cmd) |
|
621 | 621 | self.queued.append(cmd) |
|
622 | 622 | else: |
|
623 | 623 | # log.msg("No current commands, running: %r" % cmd) |
|
624 | 624 | self.currentCommand = cmd |
|
625 | 625 | self.runCurrentCommand() |
|
626 | 626 | return d |
|
627 | 627 | |
|
628 | 628 | def runCurrentCommand(self): |
|
629 | 629 | """Run current command.""" |
|
630 | 630 | |
|
631 | 631 | cmd = self.currentCommand |
|
632 | 632 | f = getattr(self.engine, cmd.remoteMethod, None) |
|
633 | 633 | if f: |
|
634 | 634 | d = f(*cmd.args, **cmd.kwargs) |
|
635 | 635 | if cmd.remoteMethod is 'execute': |
|
636 | 636 | d.addCallback(self.saveResult) |
|
637 | 637 | d.addCallback(self.finishCommand) |
|
638 | 638 | d.addErrback(self.abortCommand) |
|
639 | 639 | else: |
|
640 | 640 | return defer.fail(AttributeError(cmd.remoteMethod)) |
|
641 | 641 | |
|
642 | 642 | def _flushQueue(self): |
|
643 | 643 | """Pop next command in queue and run it.""" |
|
644 | 644 | |
|
645 | 645 | if len(self.queued) > 0: |
|
646 | 646 | self.currentCommand = self.queued.pop(0) |
|
647 | 647 | self.runCurrentCommand() |
|
648 | 648 | |
|
649 | 649 | def saveResult(self, result): |
|
650 | 650 | """Put the result in the history.""" |
|
651 | 651 | self.history[result['number']] = result |
|
652 | 652 | return result |
|
653 | 653 | |
|
654 | 654 | def finishCommand(self, result): |
|
655 | 655 | """Finish currrent command.""" |
|
656 | 656 | |
|
657 | 657 | # The order of these commands is absolutely critical. |
|
658 | 658 | self.currentCommand.handleResult(result) |
|
659 | 659 | self.currentCommand.finished = True |
|
660 | 660 | self._flushQueue() |
|
661 | 661 | return result |
|
662 | 662 | |
|
663 | 663 | def abortCommand(self, reason): |
|
664 | 664 | """Abort current command. |
|
665 | 665 | |
|
666 | 666 | This eats the Failure but first passes it onto the Deferred that the |
|
667 | 667 | user has. |
|
668 | 668 | |
|
669 | 669 | It also clear out the queue so subsequence commands don't run. |
|
670 | 670 | """ |
|
671 | 671 | |
|
672 | 672 | # The order of these 3 commands is absolutely critical. The currentCommand |
|
673 | 673 | # must first be marked as finished BEFORE the queue is cleared and before |
|
674 | 674 | # the current command is sent the failure. |
|
675 | 675 | # Also, the queue must be cleared BEFORE the current command is sent the Failure |
|
676 | 676 | # otherwise the errback chain could trigger new commands to be added to the |
|
677 | 677 | # queue before we clear it. We should clear ONLY the commands that were in |
|
678 | 678 | # the queue when the error occured. |
|
679 | 679 | self.currentCommand.finished = True |
|
680 | 680 | s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs) |
|
681 | 681 | self.clear_queue(msg=s) |
|
682 | 682 | self.currentCommand.handleError(reason) |
|
683 | 683 | |
|
684 | 684 | return None |
|
685 | 685 | |
|
686 | 686 | #--------------------------------------------------------------------------- |
|
687 | 687 | # IEngineCore methods |
|
688 | 688 | #--------------------------------------------------------------------------- |
|
689 | 689 | |
|
690 | 690 | @queue |
|
691 | 691 | def execute(self, lines): |
|
692 | 692 | pass |
|
693 | 693 | |
|
694 | 694 | @queue |
|
695 | 695 | def push(self, namespace): |
|
696 | 696 | pass |
|
697 | 697 | |
|
698 | 698 | @queue |
|
699 | 699 | def pull(self, keys): |
|
700 | 700 | pass |
|
701 | 701 | |
|
702 | 702 | @queue |
|
703 | 703 | def push_function(self, namespace): |
|
704 | 704 | pass |
|
705 | 705 | |
|
706 | 706 | @queue |
|
707 | 707 | def pull_function(self, keys): |
|
708 | 708 | pass |
|
709 | 709 | |
|
710 | 710 | def get_result(self, i=None): |
|
711 | 711 | if i is None: |
|
712 | 712 | i = max(self.history.keys()+[None]) |
|
713 | 713 | |
|
714 | 714 | cmd = self.history.get(i, None) |
|
715 | 715 | # Uncomment this line to disable chaching of results |
|
716 | 716 | #cmd = None |
|
717 | 717 | if cmd is None: |
|
718 | 718 | return self.submitCommand(Command('get_result', i)) |
|
719 | 719 | else: |
|
720 | 720 | return defer.succeed(cmd) |
|
721 | 721 | |
|
722 | 722 | def reset(self): |
|
723 | 723 | self.clear_queue() |
|
724 | 724 | self.history = {} # reset the cache - I am not sure we should do this |
|
725 | 725 | return self.submitCommand(Command('reset')) |
|
726 | 726 | |
|
727 | 727 | def kill(self): |
|
728 | 728 | self.clear_queue() |
|
729 | 729 | return self.submitCommand(Command('kill')) |
|
730 | 730 | |
|
731 | 731 | @queue |
|
732 | 732 | def keys(self): |
|
733 | 733 | pass |
|
734 | 734 | |
|
735 | 735 | #--------------------------------------------------------------------------- |
|
736 | 736 | # IEngineSerialized methods |
|
737 | 737 | #--------------------------------------------------------------------------- |
|
738 | 738 | |
|
739 | 739 | @queue |
|
740 | 740 | def push_serialized(self, namespace): |
|
741 | 741 | pass |
|
742 | 742 | |
|
743 | 743 | @queue |
|
744 | 744 | def pull_serialized(self, keys): |
|
745 | 745 | pass |
|
746 | 746 | |
|
747 | 747 | #--------------------------------------------------------------------------- |
|
748 | 748 | # IEngineProperties methods |
|
749 | 749 | #--------------------------------------------------------------------------- |
|
750 | 750 | |
|
751 | 751 | @queue |
|
752 | 752 | def set_properties(self, namespace): |
|
753 | 753 | pass |
|
754 | 754 | |
|
755 | 755 | @queue |
|
756 | 756 | def get_properties(self, keys=None): |
|
757 | 757 | pass |
|
758 | 758 | |
|
759 | 759 | @queue |
|
760 | 760 | def del_properties(self, keys): |
|
761 | 761 | pass |
|
762 | 762 | |
|
763 | 763 | @queue |
|
764 | 764 | def has_properties(self, keys): |
|
765 | 765 | pass |
|
766 | 766 | |
|
767 | 767 | @queue |
|
768 | 768 | def clear_properties(self): |
|
769 | 769 | pass |
|
770 | 770 | |
|
771 | 771 | #--------------------------------------------------------------------------- |
|
772 | 772 | # IQueuedEngine methods |
|
773 | 773 | #--------------------------------------------------------------------------- |
|
774 | 774 | |
|
775 | 775 | def clear_queue(self, msg=''): |
|
776 | 776 | """Clear the queue, but doesn't cancel the currently running commmand.""" |
|
777 | 777 | |
|
778 | 778 | for cmd in self.queued: |
|
779 | 779 | cmd.deferred.errback(failure.Failure(error.QueueCleared(msg))) |
|
780 | 780 | self.queued = [] |
|
781 | 781 | return defer.succeed(None) |
|
782 | 782 | |
|
783 | 783 | def queue_status(self): |
|
784 | 784 | if self.currentCommand is not None: |
|
785 | 785 | if self.currentCommand.finished: |
|
786 | 786 | pending = repr(None) |
|
787 | 787 | else: |
|
788 | 788 | pending = repr(self.currentCommand) |
|
789 | 789 | else: |
|
790 | 790 | pending = repr(None) |
|
791 | 791 | dikt = {'queue':map(repr,self.queued), 'pending':pending} |
|
792 | 792 | return defer.succeed(dikt) |
|
793 | 793 | |
|
794 | 794 | def register_failure_observer(self, obs): |
|
795 | 795 | self.failureObservers.append(obs) |
|
796 | 796 | |
|
797 | 797 | def unregister_failure_observer(self, obs): |
|
798 | 798 | self.failureObservers.remove(obs) |
|
799 | 799 | |
|
800 | 800 | |
|
801 | 801 | # Now register QueuedEngine as an adpater class that makes an IEngineBase into a |
|
802 | 802 | # IEngineQueued. |
|
803 | 803 | components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued) |
|
804 | 804 | |
|
805 | 805 | |
|
806 | 806 | class Command(object): |
|
807 | 807 | """A command object that encapslates queued commands. |
|
808 | 808 | |
|
809 | 809 | This class basically keeps track of a command that has been queued |
|
810 | 810 | in a QueuedEngine. It manages the deferreds and hold the method to be called |
|
811 | 811 | and the arguments to that method. |
|
812 | 812 | """ |
|
813 | 813 | |
|
814 | 814 | |
|
815 | 815 | def __init__(self, remoteMethod, *args, **kwargs): |
|
816 | 816 | """Build a new Command object.""" |
|
817 | 817 | |
|
818 | 818 | self.remoteMethod = remoteMethod |
|
819 | 819 | self.args = args |
|
820 | 820 | self.kwargs = kwargs |
|
821 | 821 | self.finished = False |
|
822 | 822 | |
|
823 | 823 | def setDeferred(self, d): |
|
824 | 824 | """Sets the deferred attribute of the Command.""" |
|
825 | 825 | |
|
826 | 826 | self.deferred = d |
|
827 | 827 | |
|
828 | 828 | def __repr__(self): |
|
829 | 829 | if not self.args: |
|
830 | 830 | args = '' |
|
831 | 831 | else: |
|
832 | 832 | args = str(self.args)[1:-2] #cut off (...,) |
|
833 | 833 | for k,v in self.kwargs.iteritems(): |
|
834 | 834 | if args: |
|
835 | 835 | args += ', ' |
|
836 | 836 | args += '%s=%r' %(k,v) |
|
837 | 837 | return "%s(%s)" %(self.remoteMethod, args) |
|
838 | 838 | |
|
839 | 839 | def handleResult(self, result): |
|
840 | 840 | """When the result is ready, relay it to self.deferred.""" |
|
841 | 841 | |
|
842 | 842 | self.deferred.callback(result) |
|
843 | 843 | |
|
844 | 844 | def handleError(self, reason): |
|
845 | 845 | """When an error has occured, relay it to self.deferred.""" |
|
846 | 846 | |
|
847 | 847 | self.deferred.errback(reason) |
|
848 | 848 | |
|
849 | 849 | class ThreadedEngineService(EngineService): |
|
850 |
"""An EngineService subclass that defers execute commands to a separate |
|
|
850 | """An EngineService subclass that defers execute commands to a separate | |
|
851 | thread. | |
|
851 | 852 | |
|
852 |
ThreadedEngineService uses twisted.internet.threads.deferToThread to |
|
|
853 |
requests to a separate thread. GUI frontends may want to |
|
|
854 | the engine in an IPython.frontend.frontendbase.FrontEndBase subclass to prevent | |
|
853 | ThreadedEngineService uses twisted.internet.threads.deferToThread to | |
|
854 | defer execute requests to a separate thread. GUI frontends may want to | |
|
855 | use ThreadedEngineService as the engine in an | |
|
856 | IPython.frontend.frontendbase.FrontEndBase subclass to prevent | |
|
855 | 857 | block execution from blocking the GUI thread. |
|
856 | 858 | """ |
|
857 | 859 | |
|
858 | 860 | zi.implements(IEngineBase) |
|
859 | 861 | |
|
860 | 862 | def __init__(self, shellClass=Interpreter, mpi=None): |
|
861 | 863 | EngineService.__init__(self, shellClass, mpi) |
|
862 | 864 | |
|
863 | 865 | |
|
864 | 866 | def execute(self, lines): |
|
865 | 867 | # Only import this if we are going to use this class |
|
866 | 868 | from twisted.internet import threads |
|
867 | 869 | |
|
868 | 870 | msg = {'engineid':self.id, |
|
869 | 871 | 'method':'execute', |
|
870 | 872 | 'args':[lines]} |
|
871 | 873 | |
|
872 | 874 | d = threads.deferToThread(self.shell.execute, lines) |
|
873 | 875 | d.addCallback(self.addIDToResult) |
|
874 | 876 | return d |
|
1 | NO CONTENT: file renamed from docs/ChangeLog to docs/attic/ChangeLog |
@@ -1,161 +1,162 b'' | |||
|
1 | 1 | .. _changes: |
|
2 | 2 | |
|
3 | 3 | ========== |
|
4 | 4 | What's new |
|
5 | 5 | ========== |
|
6 | 6 | |
|
7 | 7 | .. contents:: |
|
8 | 8 | |
|
9 | 9 | Release 0.9 |
|
10 | 10 | =========== |
|
11 | 11 | |
|
12 | 12 | New features |
|
13 | 13 | ------------ |
|
14 | 14 | |
|
15 | 15 | * All of the parallel computing capabilities from `ipython1-dev` have been merged into |
|
16 | 16 | IPython proper. This resulted in the following new subpackages: |
|
17 | 17 | :mod:`IPython.kernel`, :mod:`IPython.kernel.core`, :mod:`IPython.config`, |
|
18 | 18 | :mod:`IPython.tools` and :mod:`IPython.testing`. |
|
19 | 19 | * As part of merging in the `ipython1-dev` stuff, the `setup.py` script and friends |
|
20 | 20 | have been completely refactored. Now we are checking for dependencies using |
|
21 | 21 | the approach that matplotlib uses. |
|
22 | 22 | * The documentation has been completely reorganized to accept the documentation |
|
23 | 23 | from `ipython1-dev`. |
|
24 | 24 | * We have switched to using Foolscap for all of our network protocols in |
|
25 | 25 | :mod:`IPython.kernel`. This gives us secure connections that are both encrypted |
|
26 | 26 | and authenticated. |
|
27 | 27 | * We have a brand new `COPYING.txt` files that describes the IPython license |
|
28 | 28 | and copyright. The biggest change is that we are putting "The IPython |
|
29 | 29 | Development Team" as the copyright holder. We give more details about exactly |
|
30 | 30 | what this means in this file. All developer should read this and use the new |
|
31 | 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 | 34 | Bug fixes |
|
34 | 35 | --------- |
|
35 | 36 | |
|
36 | 37 | * A few subpackages has missing `__init__.py` files. |
|
37 | 38 | * The documentation is only created is Sphinx is found. Previously, the `setup.py` |
|
38 | 39 | script would fail if it was missing. |
|
39 | 40 | |
|
40 | 41 | Backwards incompatible changes |
|
41 | 42 | ------------------------------ |
|
42 | 43 | |
|
43 | 44 | * IPython has a larger set of dependencies if you want all of its capabilities. |
|
44 | 45 | See the `setup.py` script for details. |
|
45 | 46 | * The constructors for :class:`IPython.kernel.client.MultiEngineClient` and |
|
46 | 47 | :class:`IPython.kernel.client.TaskClient` no longer take the (ip,port) tuple. |
|
47 | 48 | Instead they take the filename of a file that contains the FURL for that |
|
48 | 49 | client. If the FURL file is in your IPYTHONDIR, it will be found automatically |
|
49 | 50 | and the constructor can be left empty. |
|
50 | 51 | * The asynchronous clients in :mod:`IPython.kernel.asyncclient` are now created |
|
51 | 52 | using the factory functions :func:`get_multiengine_client` and |
|
52 | 53 | :func:`get_task_client`. These return a `Deferred` to the actual client. |
|
53 | 54 | * The command line options to `ipcontroller` and `ipengine` have changed to |
|
54 | 55 | reflect the new Foolscap network protocol and the FURL files. Please see the |
|
55 | 56 | help for these scripts for details. |
|
56 | 57 | * The configuration files for the kernel have changed because of the Foolscap stuff. |
|
57 | 58 | If you were using custom config files before, you should delete them and regenerate |
|
58 | 59 | new ones. |
|
59 | 60 | |
|
60 | 61 | Changes merged in from IPython1 |
|
61 | 62 | ------------------------------- |
|
62 | 63 | |
|
63 | 64 | New features |
|
64 | 65 | ............ |
|
65 | 66 | |
|
66 | 67 | * Much improved ``setup.py`` and ``setupegg.py`` scripts. Because Twisted |
|
67 | 68 | and zope.interface are now easy installable, we can declare them as dependencies |
|
68 | 69 | in our setupegg.py script. |
|
69 | 70 | * IPython is now compatible with Twisted 2.5.0 and 8.x. |
|
70 | 71 | * Added a new example of how to use :mod:`ipython1.kernel.asynclient`. |
|
71 | 72 | * Initial draft of a process daemon in :mod:`ipython1.daemon`. This has not |
|
72 | 73 | been merged into IPython and is still in `ipython1-dev`. |
|
73 | 74 | * The ``TaskController`` now has methods for getting the queue status. |
|
74 | 75 | * The ``TaskResult`` objects not have information about how long the task |
|
75 | 76 | took to run. |
|
76 | 77 | * We are attaching additional attributes to exceptions ``(_ipython_*)`` that |
|
77 | 78 | we use to carry additional info around. |
|
78 | 79 | * New top-level module :mod:`asyncclient` that has asynchronous versions (that |
|
79 | 80 | return deferreds) of the client classes. This is designed to users who want |
|
80 | 81 | to run their own Twisted reactor |
|
81 | 82 | * All the clients in :mod:`client` are now based on Twisted. This is done by |
|
82 | 83 | running the Twisted reactor in a separate thread and using the |
|
83 | 84 | :func:`blockingCallFromThread` function that is in recent versions of Twisted. |
|
84 | 85 | * Functions can now be pushed/pulled to/from engines using |
|
85 | 86 | :meth:`MultiEngineClient.push_function` and :meth:`MultiEngineClient.pull_function`. |
|
86 | 87 | * Gather/scatter are now implemented in the client to reduce the work load |
|
87 | 88 | of the controller and improve performance. |
|
88 | 89 | * Complete rewrite of the IPython docuementation. All of the documentation |
|
89 | 90 | from the IPython website has been moved into docs/source as restructured |
|
90 | 91 | text documents. PDF and HTML documentation are being generated using |
|
91 | 92 | Sphinx. |
|
92 | 93 | * New developer oriented documentation: development guidelines and roadmap. |
|
93 | 94 | * Traditional ``ChangeLog`` has been changed to a more useful ``changes.txt`` file |
|
94 | 95 | that is organized by release and is meant to provide something more relevant |
|
95 | 96 | for users. |
|
96 | 97 | |
|
97 | 98 | Bug fixes |
|
98 | 99 | ......... |
|
99 | 100 | |
|
100 | 101 | * Created a proper ``MANIFEST.in`` file to create source distributions. |
|
101 | 102 | * Fixed a bug in the ``MultiEngine`` interface. Previously, multi-engine |
|
102 | 103 | actions were being collected with a :class:`DeferredList` with |
|
103 | 104 | ``fireononeerrback=1``. This meant that methods were returning |
|
104 | 105 | before all engines had given their results. This was causing extremely odd |
|
105 | 106 | bugs in certain cases. To fix this problem, we have 1) set |
|
106 | 107 | ``fireononeerrback=0`` to make sure all results (or exceptions) are in |
|
107 | 108 | before returning and 2) introduced a :exc:`CompositeError` exception |
|
108 | 109 | that wraps all of the engine exceptions. This is a huge change as it means |
|
109 | 110 | that users will have to catch :exc:`CompositeError` rather than the actual |
|
110 | 111 | exception. |
|
111 | 112 | |
|
112 | 113 | Backwards incompatible changes |
|
113 | 114 | .............................. |
|
114 | 115 | |
|
115 | 116 | * All names have been renamed to conform to the lowercase_with_underscore |
|
116 | 117 | convention. This will require users to change references to all names like |
|
117 | 118 | ``queueStatus`` to ``queue_status``. |
|
118 | 119 | * Previously, methods like :meth:`MultiEngineClient.push` and |
|
119 | 120 | :meth:`MultiEngineClient.push` used ``*args`` and ``**kwargs``. This was |
|
120 | 121 | becoming a problem as we weren't able to introduce new keyword arguments into |
|
121 | 122 | the API. Now these methods simple take a dict or sequence. This has also allowed |
|
122 | 123 | us to get rid of the ``*All`` methods like :meth:`pushAll` and :meth:`pullAll`. |
|
123 | 124 | These things are now handled with the ``targets`` keyword argument that defaults |
|
124 | 125 | to ``'all'``. |
|
125 | 126 | * The :attr:`MultiEngineClient.magicTargets` has been renamed to |
|
126 | 127 | :attr:`MultiEngineClient.targets`. |
|
127 | 128 | * All methods in the MultiEngine interface now accept the optional keyword argument |
|
128 | 129 | ``block``. |
|
129 | 130 | * Renamed :class:`RemoteController` to :class:`MultiEngineClient` and |
|
130 | 131 | :class:`TaskController` to :class:`TaskClient`. |
|
131 | 132 | * Renamed the top-level module from :mod:`api` to :mod:`client`. |
|
132 | 133 | * Most methods in the multiengine interface now raise a :exc:`CompositeError` exception |
|
133 | 134 | that wraps the user's exceptions, rather than just raising the raw user's exception. |
|
134 | 135 | * Changed the ``setupNS`` and ``resultNames`` in the ``Task`` class to ``push`` |
|
135 | 136 | and ``pull``. |
|
136 | 137 | |
|
137 | 138 | Release 0.8.4 |
|
138 | 139 | ============= |
|
139 | 140 | |
|
140 | 141 | Someone needs to describe what went into 0.8.4. |
|
141 | 142 | |
|
142 | 143 | Release 0.8.2 |
|
143 | 144 | ============= |
|
144 | 145 | |
|
145 | 146 | * %pushd/%popd behave differently; now "pushd /foo" pushes CURRENT directory |
|
146 | 147 | and jumps to /foo. The current behaviour is closer to the documented |
|
147 | 148 | behaviour, and should not trip anyone. |
|
148 | 149 | |
|
149 | 150 | Release 0.8.3 |
|
150 | 151 | ============= |
|
151 | 152 | |
|
152 | 153 | * pydb is now disabled by default (due to %run -d problems). You can enable |
|
153 | 154 | it by passing -pydb command line argument to IPython. Note that setting |
|
154 | 155 | it in config file won't work. |
|
155 | 156 | |
|
156 | 157 | Older releases |
|
157 | 158 | ============== |
|
158 | 159 | |
|
159 | 160 | Changes in earlier releases of IPython are described in the older file ``ChangeLog``. |
|
160 | 161 | Please refer to this document for details. |
|
161 | 162 |
@@ -1,315 +1,360 b'' | |||
|
1 | 1 | .. _development: |
|
2 | 2 | |
|
3 | 3 | ================================== |
|
4 | 4 | IPython development guidelines |
|
5 | 5 | ================================== |
|
6 | 6 | |
|
7 | 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 | 10 | Overview |
|
27 | 11 | ======== |
|
28 | 12 | |
|
29 | 13 | IPython is the next generation of IPython. It is named such for two reasons: |
|
30 | 14 | |
|
31 | 15 | - Eventually, IPython will become IPython version 1.0. |
|
32 | 16 | - This new code base needs to be able to co-exist with the existing IPython until |
|
33 | 17 | it is a full replacement for it. Thus we needed a different name. We couldn't |
|
34 | 18 | use ``ipython`` (lowercase) as some files systems are case insensitive. |
|
35 | 19 | |
|
36 | 20 | There are two, no three, main goals of the IPython effort: |
|
37 | 21 | |
|
38 | 22 | 1. Clean up the existing codebase and write lots of tests. |
|
39 | 23 | 2. Separate the core functionality of IPython from the terminal to enable IPython |
|
40 | 24 | to be used from within a variety of GUI applications. |
|
41 | 25 | 3. Implement a system for interactive parallel computing. |
|
42 | 26 | |
|
43 | 27 | While the third goal may seem a bit unrelated to the main focus of IPython, it turns |
|
44 | 28 | out that the technologies required for this goal are nearly identical with those |
|
45 | 29 | required for goal two. This is the main reason the interactive parallel computing |
|
46 | 30 | capabilities are being put into IPython proper. Currently the third of these goals is |
|
47 | 31 | furthest along. |
|
48 | 32 | |
|
49 | 33 | This document describes IPython from the perspective of developers. |
|
50 | 34 | |
|
51 | 35 | |
|
52 | 36 | Project organization |
|
53 | 37 | ==================== |
|
54 | 38 | |
|
55 | 39 | Subpackages |
|
56 | 40 | ----------- |
|
57 | 41 | |
|
58 | 42 | IPython is organized into semi self-contained subpackages. Each of the subpackages will have its own: |
|
59 | 43 | |
|
60 | 44 | - **Dependencies**. One of the most important things to keep in mind in |
|
61 | 45 | partitioning code amongst subpackages, is that they should be used to cleanly |
|
62 | 46 | encapsulate dependencies. |
|
63 | 47 | - **Tests**. Each subpackage shoud have its own ``tests`` subdirectory that |
|
64 | 48 | contains all of the tests for that package. For information about writing tests |
|
65 | 49 | for IPython, see the `Testing System`_ section of this document. |
|
66 | 50 | - **Configuration**. Each subpackage should have its own ``config`` subdirectory |
|
67 | 51 | that contains the configuration information for the components of the |
|
68 | 52 | subpackage. For information about how the IPython configuration system |
|
69 | 53 | works, see the `Configuration System`_ section of this document. |
|
70 | 54 | - **Scripts**. Each subpackage should have its own ``scripts`` subdirectory that |
|
71 | 55 | contains all of the command line scripts associated with the subpackage. |
|
72 | 56 | |
|
73 | 57 | Installation and dependencies |
|
74 | 58 | ----------------------------- |
|
75 | 59 | |
|
76 | 60 | IPython will not use `setuptools`_ for installation. Instead, we will use standard |
|
77 | 61 | ``setup.py`` scripts that use `distutils`_. While there are a number a extremely nice |
|
78 | 62 | features that `setuptools`_ has (like namespace packages), the current implementation |
|
79 | 63 | of `setuptools`_ has performance problems, particularly on shared file systems. In |
|
80 | 64 | particular, when Python packages are installed on NSF file systems, import times |
|
81 | 65 | become much too long (up towards 10 seconds). |
|
82 | 66 | |
|
83 | 67 | Because IPython is being used extensively in the context of high performance |
|
84 | 68 | computing, where performance is critical but shared file systems are common, we feel |
|
85 | 69 | these performance hits are not acceptable. Thus, until the performance problems |
|
86 | 70 | associated with `setuptools`_ are addressed, we will stick with plain `distutils`_. We |
|
87 | 71 | are hopeful that these problems will be addressed and that we will eventually begin |
|
88 | 72 | using `setuptools`_. Because of this, we are trying to organize IPython in a way that |
|
89 | 73 | will make the eventual transition to `setuptools`_ as painless as possible. |
|
90 | 74 | |
|
91 | 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 | 77 | - Distinguish between required and optional dependencies. However, the required |
|
94 | 78 | dependencies for IPython should be only the Python standard library. |
|
95 | 79 | - Upon installation check to see which optional dependencies are present and tell |
|
96 | 80 | the user which parts of IPython need which optional dependencies. |
|
97 | 81 | |
|
98 | 82 | It is absolutely critical that each subpackage of IPython has a clearly specified set |
|
99 | 83 | of dependencies and that dependencies are not carelessly inherited from other IPython |
|
100 | 84 | subpackages. Furthermore, tests that have certain dependencies should not fail if |
|
101 | 85 | those dependencies are not present. Instead they should be skipped and print a |
|
102 | 86 | message. |
|
103 | 87 | |
|
104 | 88 | .. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools |
|
105 | 89 | .. _distutils: http://docs.python.org/lib/module-distutils.html |
|
106 | 90 | .. _Matplotlib: http://matplotlib.sourceforge.net/ |
|
107 | 91 | |
|
108 | 92 | Specific subpackages |
|
109 | 93 | -------------------- |
|
110 | 94 | |
|
111 | 95 | ``core`` |
|
112 | 96 | This is the core functionality of IPython that is independent of the |
|
113 | 97 | terminal, network and GUIs. Most of the code that is in the current |
|
114 | 98 | IPython trunk will be refactored, cleaned up and moved here. |
|
115 | 99 | |
|
116 | 100 | ``kernel`` |
|
117 | 101 | The enables the IPython core to be expose to a the network. This is |
|
118 | 102 | also where all of the parallel computing capabilities are to be found. |
|
119 | 103 | |
|
120 | 104 | ``config`` |
|
121 | 105 | The configuration package used by IPython. |
|
122 | 106 | |
|
123 | 107 | ``frontends`` |
|
124 | 108 | The various frontends for IPython. A frontend is the end-user application |
|
125 | 109 | that exposes the capabilities of IPython to the user. The most basic frontend |
|
126 | 110 | will simply be a terminal based application that looks just like today 's |
|
127 | 111 | IPython. Other frontends will likely be more powerful and based on GUI toolkits. |
|
128 | 112 | |
|
129 | 113 | ``notebook`` |
|
130 | 114 | An application that allows users to work with IPython notebooks. |
|
131 | 115 | |
|
132 | 116 | ``tools`` |
|
133 | 117 | This is where general utilities go. |
|
134 | 118 | |
|
135 | 119 | |
|
136 | 120 | Version control |
|
137 | 121 | =============== |
|
138 | 122 | |
|
139 |
In the past, IPython development has been done using `Subversion`__. |
|
|
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 | 185 | .. __: http://subversion.tigris.org/ |
|
142 | 186 | .. __: http://bazaar-vcs.org/ |
|
143 | 187 | .. __: http://www.launchpad.net/ipython |
|
188 | .. __: http://doc.bazaar-vcs.org/bzr.dev/en/user-guide/index.html | |
|
144 | 189 | |
|
145 | 190 | Documentation |
|
146 | 191 | ============= |
|
147 | 192 | |
|
148 | 193 | Standalone documentation |
|
149 | 194 | ------------------------ |
|
150 | 195 | |
|
151 | 196 | All standalone documentation should be written in plain text (``.txt``) files using |
|
152 | 197 | `reStructuredText`_ for markup and formatting. All such documentation should be placed |
|
153 | 198 | in the top level directory ``docs`` of the IPython source tree. Or, when appropriate, |
|
154 | 199 | a suitably named subdirectory should be used. The documentation in this location will |
|
155 | 200 | serve as the main source for IPython documentation and all existing documentation |
|
156 | 201 | should be converted to this format. |
|
157 | 202 | |
|
158 | 203 | In the future, the text files in the ``docs`` directory will be used to generate all |
|
159 | 204 | forms of documentation for IPython. This include documentation on the IPython website |
|
160 | 205 | as well as *pdf* documentation. |
|
161 | 206 | |
|
162 | 207 | .. _reStructuredText: http://docutils.sourceforge.net/rst.html |
|
163 | 208 | |
|
164 | 209 | Docstring format |
|
165 | 210 | ---------------- |
|
166 | 211 | |
|
167 | 212 | Good docstrings are very important. All new code will use `Epydoc`_ for generating API |
|
168 | 213 | docs, so we will follow the `Epydoc`_ conventions. More specifically, we will use |
|
169 | 214 | `reStructuredText`_ for markup and formatting, since it is understood by a wide |
|
170 | 215 | variety of tools. This means that if in the future we have any reason to change from |
|
171 | 216 | `Epydoc`_ to something else, we'll have fewer transition pains. |
|
172 | 217 | |
|
173 | 218 | Details about using `reStructuredText`_ for docstrings can be found `here |
|
174 | 219 | <http://epydoc.sourceforge.net/manual-othermarkup.html>`_. |
|
175 | 220 | |
|
176 | 221 | .. _Epydoc: http://epydoc.sourceforge.net/ |
|
177 | 222 | |
|
178 | 223 | Additional PEPs of interest regarding documentation of code: |
|
179 | 224 | |
|
180 | 225 | - `Docstring Conventions <http://www.python.org/peps/pep-0257.html>`_ |
|
181 | 226 | - `Docstring Processing System Framework <http://www.python.org/peps/pep-0256.html>`_ |
|
182 | 227 | - `Docutils Design Specification <http://www.python.org/peps/pep-0258.html>`_ |
|
183 | 228 | |
|
184 | 229 | |
|
185 | 230 | Coding conventions |
|
186 | 231 | ================== |
|
187 | 232 | |
|
188 | 233 | General |
|
189 | 234 | ------- |
|
190 | 235 | |
|
191 | 236 | In general, we'll try to follow the standard Python style conventions as described here: |
|
192 | 237 | |
|
193 | 238 | - `Style Guide for Python Code <http://www.python.org/peps/pep-0008.html>`_ |
|
194 | 239 | |
|
195 | 240 | |
|
196 | 241 | Other comments: |
|
197 | 242 | |
|
198 | 243 | - In a large file, top level classes and functions should be |
|
199 | 244 | separated by 2-3 lines to make it easier to separate them visually. |
|
200 | 245 | - Use 4 spaces for indentation. |
|
201 | 246 | - Keep the ordering of methods the same in classes that have the same |
|
202 | 247 | methods. This is particularly true for classes that implement |
|
203 | 248 | similar interfaces and for interfaces that are similar. |
|
204 | 249 | |
|
205 | 250 | Naming conventions |
|
206 | 251 | ------------------ |
|
207 | 252 | |
|
208 | 253 | In terms of naming conventions, we'll follow the guidelines from the `Style Guide for |
|
209 | 254 | Python Code`_. |
|
210 | 255 | |
|
211 | 256 | For all new IPython code (and much existing code is being refactored), we'll use: |
|
212 | 257 | |
|
213 | 258 | - All ``lowercase`` module names. |
|
214 | 259 | |
|
215 | 260 | - ``CamelCase`` for class names. |
|
216 | 261 | |
|
217 | 262 | - ``lowercase_with_underscores`` for methods, functions, variables and attributes. |
|
218 | 263 | |
|
219 | 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 | 265 | convention, providing shadow names for backward compatibility in public interfaces. |
|
221 | 266 | |
|
222 | 267 | There are, however, some important exceptions to these rules. In some cases, IPython |
|
223 | 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 | 270 | - If you are subclassing a class that uses different conventions, use its |
|
226 | 271 | naming conventions throughout your subclass. Thus, if you are creating a |
|
227 | 272 | Twisted Protocol class, used Twisted's ``namingSchemeForMethodsAndAttributes.`` |
|
228 | 273 | |
|
229 | 274 | - All IPython's official interfaces should use our conventions. In some cases |
|
230 | 275 | this will mean that you need to provide shadow names (first implement ``fooBar`` |
|
231 | 276 | and then ``foo_bar = fooBar``). We want to avoid this at all costs, but it |
|
232 | 277 | will probably be necessary at times. But, please use this sparingly! |
|
233 | 278 | |
|
234 | 279 | Implementation-specific *private* methods will use ``_single_underscore_prefix``. |
|
235 | 280 | Names with a leading double underscore will *only* be used in special cases, as they |
|
236 | 281 | makes subclassing difficult (such names are not easily seen by child classes). |
|
237 | 282 | |
|
238 | 283 | Occasionally some run-in lowercase names are used, but mostly for very short names or |
|
239 | 284 | where we are implementing methods very similar to existing ones in a base class (like |
|
240 | 285 | ``runlines()`` where ``runsource()`` and ``runcode()`` had established precedent). |
|
241 | 286 | |
|
242 | 287 | The old IPython codebase has a big mix of classes and modules prefixed with an |
|
243 | 288 | explicit ``IP``. In Python this is mostly unnecessary, redundant and frowned upon, as |
|
244 | 289 | namespaces offer cleaner prefixing. The only case where this approach is justified is |
|
245 | 290 | for classes which are expected to be imported into external namespaces and a very |
|
246 | 291 | generic name (like Shell) is too likely to clash with something else. We'll need to |
|
247 | 292 | revisit this issue as we clean up and refactor the code, but in general we should |
|
248 | 293 | remove as many unnecessary ``IP``/``ip`` prefixes as possible. However, if a prefix |
|
249 | 294 | seems absolutely necessary the more specific ``IPY`` or ``ipy`` are preferred. |
|
250 | 295 | |
|
251 | 296 | .. _devel_testing: |
|
252 | 297 | |
|
253 | 298 | Testing system |
|
254 | 299 | ============== |
|
255 | 300 | |
|
256 | 301 | It is extremely important that all code contributed to IPython has tests. Tests should |
|
257 | 302 | be written as unittests, doctests or as entities that the `Nose`_ testing package will |
|
258 | 303 | find. Regardless of how the tests are written, we will use `Nose`_ for discovering and |
|
259 | 304 | running the tests. `Nose`_ will be required to run the IPython test suite, but will |
|
260 | 305 | not be required to simply use IPython. |
|
261 | 306 | |
|
262 | 307 | .. _Nose: http://code.google.com/p/python-nose/ |
|
263 | 308 | |
|
264 | 309 | Tests of `Twisted`__ using code should be written by subclassing the ``TestCase`` class |
|
265 | 310 | that comes with ``twisted.trial.unittest``. When this is done, `Nose`_ will be able to |
|
266 | 311 | run the tests and the twisted reactor will be handled correctly. |
|
267 | 312 | |
|
268 | 313 | .. __: http://www.twistedmatrix.com |
|
269 | 314 | |
|
270 | 315 | Each subpackage in IPython should have its own ``tests`` directory that contains all |
|
271 | 316 | of the tests for that subpackage. This allows each subpackage to be self-contained. If |
|
272 | 317 | a subpackage has any dependencies beyond the Python standard library, the tests for |
|
273 | 318 | that subpackage should be skipped if the dependencies are not found. This is very |
|
274 | 319 | important so users don't get tests failing simply because they don't have dependencies. |
|
275 | 320 | |
|
276 | 321 | We also need to look into use Noses ability to tag tests to allow a more modular |
|
277 | 322 | approach of running tests. |
|
278 | 323 | |
|
279 | 324 | .. _devel_config: |
|
280 | 325 | |
|
281 | 326 | Configuration system |
|
282 | 327 | ==================== |
|
283 | 328 | |
|
284 | 329 | IPython uses `.ini`_ files for configuration purposes. This represents a huge |
|
285 | 330 | improvement over the configuration system used in IPython. IPython works with these |
|
286 | 331 | files using the `ConfigObj`_ package, which IPython includes as |
|
287 | 332 | ``ipython1/external/configobj.py``. |
|
288 | 333 | |
|
289 | 334 | Currently, we are using raw `ConfigObj`_ objects themselves. Each subpackage of IPython |
|
290 | 335 | should contain a ``config`` subdirectory that contains all of the configuration |
|
291 | 336 | information for the subpackage. To see how configuration information is defined (along |
|
292 | 337 | with defaults) see at the examples in ``ipython1/kernel/config`` and |
|
293 | 338 | ``ipython1/core/config``. Likewise, to see how the configuration information is used, |
|
294 | 339 | see examples in ``ipython1/kernel/scripts/ipengine.py``. |
|
295 | 340 | |
|
296 | 341 | Eventually, we will add a new layer on top of the raw `ConfigObj`_ objects. We are |
|
297 | 342 | calling this new layer, ``tconfig``, as it will use a `Traits`_-like validation model. |
|
298 | 343 | We won't actually use `Traits`_, but will implement something similar in pure Python. |
|
299 | 344 | But, even in this new system, we will still use `ConfigObj`_ and `.ini`_ files |
|
300 | 345 | underneath the hood. Talk to Fernando if you are interested in working on this part of |
|
301 | 346 | IPython. The current prototype of ``tconfig`` is located in the IPython sandbox. |
|
302 | 347 | |
|
303 | 348 | .. _.ini: http://docs.python.org/lib/module-ConfigParser.html |
|
304 | 349 | .. _ConfigObj: http://www.voidspace.org.uk/python/configobj.html |
|
305 | 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 | 1 | # encoding: utf-8 |
|
2 | 2 | |
|
3 | 3 | """ |
|
4 | 4 | This module defines the things that are used in setup.py for building IPython |
|
5 | 5 | |
|
6 | 6 | This includes: |
|
7 | 7 | |
|
8 | 8 | * The basic arguments to setup |
|
9 | 9 | * Functions for finding things like packages, package data, etc. |
|
10 | 10 | * A function for checking dependencies. |
|
11 | 11 | """ |
|
12 | 12 | |
|
13 | 13 | __docformat__ = "restructuredtext en" |
|
14 | 14 | |
|
15 | 15 | #------------------------------------------------------------------------------- |
|
16 | 16 | # Copyright (C) 2008 The IPython Development Team |
|
17 | 17 | # |
|
18 | 18 | # Distributed under the terms of the BSD License. The full license is in |
|
19 | 19 | # the file COPYING, distributed as part of this software. |
|
20 | 20 | #------------------------------------------------------------------------------- |
|
21 | 21 | |
|
22 | 22 | #------------------------------------------------------------------------------- |
|
23 | 23 | # Imports |
|
24 | 24 | #------------------------------------------------------------------------------- |
|
25 | 25 | |
|
26 | 26 | import os, sys |
|
27 | 27 | |
|
28 | 28 | from glob import glob |
|
29 | 29 | |
|
30 | 30 | from setupext import install_data_ext |
|
31 | 31 | |
|
32 | 32 | #------------------------------------------------------------------------------- |
|
33 | 33 | # Useful globals and utility functions |
|
34 | 34 | #------------------------------------------------------------------------------- |
|
35 | 35 | |
|
36 | 36 | # A few handy globals |
|
37 | 37 | isfile = os.path.isfile |
|
38 | 38 | pjoin = os.path.join |
|
39 | 39 | |
|
40 | 40 | def oscmd(s): |
|
41 | 41 | print ">", s |
|
42 | 42 | os.system(s) |
|
43 | 43 | |
|
44 | 44 | # A little utility we'll need below, since glob() does NOT allow you to do |
|
45 | 45 | # exclusion on multiple endings! |
|
46 | 46 | def file_doesnt_endwith(test,endings): |
|
47 | 47 | """Return true if test is a file and its name does NOT end with any |
|
48 | 48 | of the strings listed in endings.""" |
|
49 | 49 | if not isfile(test): |
|
50 | 50 | return False |
|
51 | 51 | for e in endings: |
|
52 | 52 | if test.endswith(e): |
|
53 | 53 | return False |
|
54 | 54 | return True |
|
55 | 55 | |
|
56 | 56 | #--------------------------------------------------------------------------- |
|
57 | 57 | # Basic project information |
|
58 | 58 | #--------------------------------------------------------------------------- |
|
59 | 59 | |
|
60 | 60 | # Release.py contains version, authors, license, url, keywords, etc. |
|
61 | 61 | execfile(pjoin('IPython','Release.py')) |
|
62 | 62 | |
|
63 | 63 | # Create a dict with the basic information |
|
64 | 64 | # This dict is eventually passed to setup after additional keys are added. |
|
65 | 65 | setup_args = dict( |
|
66 | 66 | name = name, |
|
67 | 67 | version = version, |
|
68 | 68 | description = description, |
|
69 | 69 | long_description = long_description, |
|
70 | 70 | author = author, |
|
71 | 71 | author_email = author_email, |
|
72 | 72 | url = url, |
|
73 | 73 | download_url = download_url, |
|
74 | 74 | license = license, |
|
75 | 75 | platforms = platforms, |
|
76 | 76 | keywords = keywords, |
|
77 | 77 | cmdclass = {'install_data': install_data_ext}, |
|
78 | 78 | ) |
|
79 | 79 | |
|
80 | 80 | |
|
81 | 81 | #--------------------------------------------------------------------------- |
|
82 | 82 | # Find packages |
|
83 | 83 | #--------------------------------------------------------------------------- |
|
84 | 84 | |
|
85 | 85 | def add_package(packages, pname, config=False, tests=False, scripts=False, others=None): |
|
86 | 86 | """ |
|
87 | 87 | Add a package to the list of packages, including certain subpackages. |
|
88 | 88 | """ |
|
89 | 89 | packages.append('.'.join(['IPython',pname])) |
|
90 | 90 | if config: |
|
91 | 91 | packages.append('.'.join(['IPython',pname,'config'])) |
|
92 | 92 | if tests: |
|
93 | 93 | packages.append('.'.join(['IPython',pname,'tests'])) |
|
94 | 94 | if scripts: |
|
95 | 95 | packages.append('.'.join(['IPython',pname,'scripts'])) |
|
96 | 96 | if others is not None: |
|
97 | 97 | for o in others: |
|
98 | 98 | packages.append('.'.join(['IPython',pname,o])) |
|
99 | 99 | |
|
100 | 100 | def find_packages(): |
|
101 | 101 | """ |
|
102 | 102 | Find all of IPython's packages. |
|
103 | 103 | """ |
|
104 | 104 | packages = ['IPython'] |
|
105 | 105 | add_package(packages, 'config', tests=True) |
|
106 | 106 | add_package(packages , 'Extensions') |
|
107 | 107 | add_package(packages, 'external') |
|
108 | 108 | add_package(packages, 'gui') |
|
109 | 109 | add_package(packages, 'gui.wx') |
|
110 | 110 | add_package(packages, 'kernel', config=True, tests=True, scripts=True) |
|
111 | 111 | add_package(packages, 'kernel.core', config=True, tests=True) |
|
112 | 112 | add_package(packages, 'testing', tests=True) |
|
113 | 113 | add_package(packages, 'tools', tests=True) |
|
114 | 114 | add_package(packages, 'UserConfig') |
|
115 | 115 | return packages |
|
116 | 116 | |
|
117 | 117 | #--------------------------------------------------------------------------- |
|
118 | 118 | # Find package data |
|
119 | 119 | #--------------------------------------------------------------------------- |
|
120 | 120 | |
|
121 | 121 | def find_package_data(): |
|
122 | 122 | """ |
|
123 | 123 | Find IPython's package_data. |
|
124 | 124 | """ |
|
125 | 125 | # This is not enough for these things to appear in an sdist. |
|
126 | 126 | # We need to muck with the MANIFEST to get this to work |
|
127 | 127 | package_data = {'IPython.UserConfig' : ['*'] } |
|
128 | 128 | return package_data |
|
129 | 129 | |
|
130 | 130 | |
|
131 | 131 | #--------------------------------------------------------------------------- |
|
132 | 132 | # Find data files |
|
133 | 133 | #--------------------------------------------------------------------------- |
|
134 | 134 | |
|
135 | 135 | def find_data_files(): |
|
136 | 136 | """ |
|
137 | 137 | Find IPython's data_files. |
|
138 | 138 | """ |
|
139 | 139 | |
|
140 | 140 | # I can't find how to make distutils create a nested dir. structure, so |
|
141 | 141 | # in the meantime do it manually. Butt ugly. |
|
142 | 142 | # Note that http://www.redbrick.dcu.ie/~noel/distutils.html, ex. 2/3, contain |
|
143 | 143 | # information on how to do this more cleanly once python 2.4 can be assumed. |
|
144 | 144 | # Thanks to Noel for the tip. |
|
145 | 145 | docdirbase = 'share/doc/ipython' |
|
146 | 146 | manpagebase = 'share/man/man1' |
|
147 | 147 | |
|
148 | 148 | # We only need to exclude from this things NOT already excluded in the |
|
149 | 149 | # MANIFEST.in file. |
|
150 | 150 | exclude = ('.sh','.1.gz') |
|
151 | 151 | # We need to figure out how we want to package all of our rst docs? |
|
152 | 152 | # docfiles = filter(lambda f:file_doesnt_endwith(f,exclude),glob('docs/*')) |
|
153 | 153 | examfiles = filter(isfile, glob('docs/examples/core/*.py')) |
|
154 | 154 | examfiles.append(filter(isfile, glob('docs/examples/kernel/*.py'))) |
|
155 | 155 | manpages = filter(isfile, glob('docs/man/*.1.gz')) |
|
156 | 156 | igridhelpfiles = filter(isfile, glob('IPython/Extensions/igrid_help.*')) |
|
157 | 157 | |
|
158 | 158 | data_files = [#('data', docdirbase, docfiles), |
|
159 | 159 | ('data', pjoin(docdirbase, 'examples'),examfiles), |
|
160 | 160 | ('data', manpagebase, manpages), |
|
161 | 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 | 168 | # Find scripts |
|
167 | 169 | #--------------------------------------------------------------------------- |
|
168 | 170 | |
|
169 | 171 | def find_scripts(): |
|
170 | 172 | """ |
|
171 | 173 | Find IPython's scripts. |
|
172 | 174 | """ |
|
173 | 175 | scripts = [] |
|
174 | 176 | scripts.append('IPython/kernel/scripts/ipengine') |
|
175 | 177 | scripts.append('IPython/kernel/scripts/ipcontroller') |
|
176 | 178 | scripts.append('IPython/kernel/scripts/ipcluster') |
|
177 | 179 | scripts.append('scripts/ipython') |
|
178 | 180 | scripts.append('scripts/pycolor') |
|
179 | 181 | scripts.append('scripts/irunner') |
|
180 | 182 | |
|
181 | 183 | # Script to be run by the windows binary installer after the default setup |
|
182 | 184 | # routine, to add shortcuts and similar windows-only things. Windows |
|
183 | 185 | # post-install scripts MUST reside in the scripts/ dir, otherwise distutils |
|
184 | 186 | # doesn't find them. |
|
185 | 187 | if 'bdist_wininst' in sys.argv: |
|
186 | 188 | if len(sys.argv) > 2 and ('sdist' in sys.argv or 'bdist_rpm' in sys.argv): |
|
187 | 189 | print >> sys.stderr,"ERROR: bdist_wininst must be run alone. Exiting." |
|
188 | 190 | sys.exit(1) |
|
189 | 191 | scripts.append('scripts/ipython_win_post_install.py') |
|
190 | 192 | |
|
191 | 193 | return scripts |
|
192 | 194 | |
|
193 | 195 | #--------------------------------------------------------------------------- |
|
194 | 196 | # Find scripts |
|
195 | 197 | #--------------------------------------------------------------------------- |
|
196 | 198 | |
|
197 | 199 | def check_for_dependencies(): |
|
198 | 200 | """Check for IPython's dependencies. |
|
199 | 201 | |
|
200 | 202 | This function should NOT be called if running under setuptools! |
|
201 | 203 | """ |
|
202 | 204 | from setupext.setupext import ( |
|
203 | 205 | print_line, print_raw, print_status, print_message, |
|
204 | 206 | check_for_zopeinterface, check_for_twisted, |
|
205 | 207 | check_for_foolscap, check_for_pyopenssl, |
|
206 | 208 | check_for_sphinx, check_for_pygments, |
|
207 | 209 | check_for_nose, check_for_pexpect |
|
208 | 210 | ) |
|
209 | 211 | print_line() |
|
210 | 212 | print_raw("BUILDING IPYTHON") |
|
211 | 213 | print_status('python', sys.version) |
|
212 | 214 | print_status('platform', sys.platform) |
|
213 | 215 | if sys.platform == 'win32': |
|
214 | 216 | print_status('Windows version', sys.getwindowsversion()) |
|
215 | 217 | |
|
216 | 218 | print_raw("") |
|
217 | 219 | print_raw("OPTIONAL DEPENDENCIES") |
|
218 | 220 | |
|
219 | 221 | check_for_zopeinterface() |
|
220 | 222 | check_for_twisted() |
|
221 | 223 | check_for_foolscap() |
|
222 | 224 | check_for_pyopenssl() |
|
223 | 225 | check_for_sphinx() |
|
224 | 226 | check_for_pygments() |
|
225 | 227 | check_for_nose() |
|
226 | 228 | check_for_pexpect() No newline at end of file |
General Comments 0
You need to be logged in to leave comments.
Login now