##// END OF EJS Templates
check for set (python 2.3 compatibility). Closes #209
vivainio -
Show More
@@ -1,1121 +1,1125 b''
1 1 # -*- coding: iso-8859-1 -*-
2 2
3 3 import ipipe, os, webbrowser, urllib
4 4 from IPython import ipapi
5 5 import wx
6 6 import wx.grid, wx.html
7 7
8 8 try:
9 9 sorted
10 10 except NameError:
11 11 from ipipe import sorted
12 try:
13 set
14 except:
15 from sets import Set as set
12 16
13 17
14 18 __all__ = ["igrid"]
15 19
16 20
17 21 help = """
18 22 <?xml version='1.0' encoding='iso-8859-1'?>
19 23 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
20 24 <html>
21 25 <head>
22 26 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
23 27 <link rel="stylesheet" href="igrid_help.css" type="text/css" />
24 28 <title>igrid help</title>
25 29 </head>
26 30 <body>
27 31 <h1>igrid help</h1>
28 32
29 33
30 34 <h2>Commands</h2>
31 35
32 36
33 37 <h3>pick (P)</h3>
34 38 <p>Pick the whole row (object is available as "_")</p>
35 39
36 40 <h3>pickattr (Shift-P)</h3>
37 41 <p>Pick the attribute under the cursor</p>
38 42
39 43 <h3>pickallattrs (Shift-C)</h3>
40 44 <p>Pick the complete column under the cursor (i.e. the attribute under the
41 45 cursor) from all currently fetched objects. These attributes will be returned
42 46 as a list.</p>
43 47
44 48 <h3>pickinput (I)</h3>
45 49 <p>Pick the current row as next input line in IPython. Additionally the row is stored as "_"</p>
46 50
47 51 <h3>pickinputattr (Shift-I)</h3>
48 52 <p>Pick the attribute under the cursor as next input line in IPython. Additionally the row is stored as "_"</p>
49 53
50 54 <h3>enter (E)</h3>
51 55 <p>Enter the object under the cursor. (what this mean depends on the object
52 56 itself, i.e. how it implements iteration). This opens a new browser 'level'.</p>
53 57
54 58 <h3>enterattr (Shift-E)</h3>
55 59 <p>Enter the attribute under the cursor.</p>
56 60
57 61 <h3>detail (D)</h3>
58 62 <p>Show a detail view of the object under the cursor. This shows the name,
59 63 type, doc string and value of the object attributes (and it might show more
60 64 attributes than in the list view, depending on the object).</p>
61 65
62 66 <h3>detailattr (Shift-D)</h3>
63 67 <p>Show a detail view of the attribute under the cursor.</p>
64 68
65 69 <h3>pickrows (M)</h3>
66 70 <p>Pick multiple selected rows (M)</p>
67 71
68 72 <h3>pickrowsattr (CTRL-M)</h3>
69 73 <p>From multiple selected rows pick the cells matching the attribute the cursor is in (CTRL-M)</p>
70 74
71 75 <h3>find (CTRL-F)</h3>
72 76 <p>Find text</p>
73 77
74 78 <h3>find_expression (CTRL-Shift-F)</h3>
75 79 <p>Find entries matching an expression</p>
76 80
77 81 <h3>find_next (F3)</h3>
78 82 <p>Find next occurrence</p>
79 83
80 84 <h3>find_previous (Shift-F3)</h3>
81 85 <p>Find previous occurrence</p>
82 86
83 87 <h3>sortattrasc (V)</h3>
84 88 <p>Sort the objects (in ascending order) using the attribute under the cursor as the sort key.</p>
85 89
86 90 <h3>sortattrdesc (Shift-V)</h3>
87 91 <p>Sort the objects (in descending order) using the attribute under the cursor as the sort key.</p>
88 92
89 93 <h3>refresh_once (R, F5)</h3>
90 94 <p>Refreshes the display by restarting the iterator</p>
91 95
92 96 <h3>refresh_every_second</h3>
93 97 <p>Refreshes the display by restarting the iterator every second until stopped by stop_refresh.</p>
94 98
95 99 <h3>refresh_interval</h3>
96 100 <p>Refreshes the display by restarting the iterator every X ms (X is a custom interval set by the user) until stopped by stop_refresh.</p>
97 101
98 102 <h3>stop_refresh</h3>
99 103 <p>Stops all refresh timers.</p>
100 104
101 105 <h3>leave (Backspace, DEL, X)</h3>
102 106 <p>Close current tab (and all the tabs to the right of the current one).</h3>
103 107
104 108 <h3>quit (ESC,Q)</h3>
105 109 <p>Quit igrid and return to the IPython prompt.</p>
106 110
107 111
108 112 <h2>Navigation</h2>
109 113
110 114
111 115 <h3>Jump to the last column of the current row (END, CTRL-E, CTRL-Right)</h3>
112 116
113 117 <h3>Jump to the first column of the current row (HOME, CTRL-A, CTRL-Left)</h3>
114 118
115 119 <h3>Move the cursor one column to the left (&lt;)</h3>
116 120
117 121 <h3>Move the cursor one column to the right (&gt;)</h3>
118 122
119 123 <h3>Jump to the first row in the current column (CTRL-Up)</h3>
120 124
121 125 <h3>Jump to the last row in the current column (CTRL-Down)</h3>
122 126
123 127 </body>
124 128 </html>
125 129
126 130 """
127 131
128 132
129 133 class IGridRenderer(wx.grid.PyGridCellRenderer):
130 134 """
131 135 This is a custom renderer for our IGridGrid
132 136 """
133 137 def __init__(self, table):
134 138 self.maxchars = 200
135 139 self.table = table
136 140 self.colormap = (
137 141 ( 0, 0, 0),
138 142 (174, 0, 0),
139 143 ( 0, 174, 0),
140 144 (174, 174, 0),
141 145 ( 0, 0, 174),
142 146 (174, 0, 174),
143 147 ( 0, 174, 174),
144 148 ( 64, 64, 64)
145 149 )
146 150
147 151 wx.grid.PyGridCellRenderer.__init__(self)
148 152
149 153 def _getvalue(self, row, col):
150 154 try:
151 155 value = self.table._displayattrs[col].value(self.table.items[row])
152 156 (align, width, text) = ipipe.xformat(value, "cell", self.maxchars)
153 157 except Exception, exc:
154 158 (align, width, text) = ipipe.xformat(exc, "cell", self.maxchars)
155 159 return (align, text)
156 160
157 161 def GetBestSize(self, grid, attr, dc, row, col):
158 162 text = grid.GetCellValue(row, col)
159 163 (align, text) = self._getvalue(row, col)
160 164 dc.SetFont(attr.GetFont())
161 165 (w, h) = dc.GetTextExtent(str(text))
162 166 return wx.Size(min(w+2, 600), h+2) # add border
163 167
164 168 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
165 169 """
166 170 Takes care of drawing everything in the cell; aligns the text
167 171 """
168 172 text = grid.GetCellValue(row, col)
169 173 (align, text) = self._getvalue(row, col)
170 174 if isSelected:
171 175 bg = grid.GetSelectionBackground()
172 176 else:
173 177 bg = ["white", (240, 240, 240)][row%2]
174 178 dc.SetTextBackground(bg)
175 179 dc.SetBrush(wx.Brush(bg, wx.SOLID))
176 180 dc.SetPen(wx.TRANSPARENT_PEN)
177 181 dc.SetFont(attr.GetFont())
178 182 dc.DrawRectangleRect(rect)
179 183 dc.SetClippingRect(rect)
180 184 # Format the text
181 185 if align == -1: # left alignment
182 186 (width, height) = dc.GetTextExtent(str(text))
183 187 x = rect[0]+1
184 188 y = rect[1]+0.5*(rect[3]-height)
185 189
186 190 for (style, part) in text:
187 191 if isSelected:
188 192 fg = grid.GetSelectionForeground()
189 193 else:
190 194 fg = self.colormap[style.fg]
191 195 dc.SetTextForeground(fg)
192 196 (w, h) = dc.GetTextExtent(part)
193 197 dc.DrawText(part, x, y)
194 198 x += w
195 199 elif align == 0: # center alignment
196 200 (width, height) = dc.GetTextExtent(str(text))
197 201 x = rect[0]+0.5*(rect[2]-width)
198 202 y = rect[1]+0.5*(rect[3]-height)
199 203 for (style, part) in text:
200 204 if isSelected:
201 205 fg = grid.GetSelectionForeground()
202 206 else:
203 207 fg = self.colormap[style.fg]
204 208 dc.SetTextForeground(fg)
205 209 (w, h) = dc.GetTextExtent(part)
206 210 dc.DrawText(part, x, y)
207 211 x += w
208 212 else: # right alignment
209 213 (width, height) = dc.GetTextExtent(str(text))
210 214 x = rect[0]+rect[2]-1
211 215 y = rect[1]+0.5*(rect[3]-height)
212 216 for (style, part) in reversed(text):
213 217 (w, h) = dc.GetTextExtent(part)
214 218 x -= w
215 219 if isSelected:
216 220 fg = grid.GetSelectionForeground()
217 221 else:
218 222 fg = self.colormap[style.fg]
219 223 dc.SetTextForeground(fg)
220 224 dc.DrawText(part, x, y)
221 225 dc.DestroyClippingRegion()
222 226
223 227 def Clone(self):
224 228 return IGridRenderer(self.table)
225 229
226 230
227 231 class IGridTable(wx.grid.PyGridTableBase):
228 232 # The data table for the ``IGridGrid``. Some dirty tricks were used here:
229 233 # ``GetValue()`` does not get any values (or at least it does not return
230 234 # anything, accessing the values is done by the renderer)
231 235 # but rather tries to fetch the objects which were requested into the table.
232 236 # General behaviour is: Fetch the first X objects. If the user scrolls down
233 237 # to the last object another bunch of X objects is fetched (if possible)
234 238 def __init__(self, input, fontsize, *attrs):
235 239 wx.grid.PyGridTableBase.__init__(self)
236 240 self.input = input
237 241 self.iterator = ipipe.xiter(input)
238 242 self.items = []
239 243 self.attrs = [ipipe.upgradexattr(attr) for attr in attrs]
240 244 self._displayattrs = self.attrs[:]
241 245 self._displayattrset = set(self.attrs)
242 246 self.fontsize = fontsize
243 247 self._fetch(1)
244 248 self.timer = wx.Timer()
245 249 self.timer.Bind(wx.EVT_TIMER, self.refresh_content)
246 250
247 251 def GetAttr(self, *args):
248 252 attr = wx.grid.GridCellAttr()
249 253 attr.SetFont(wx.Font(self.fontsize, wx.TELETYPE, wx.NORMAL, wx.NORMAL))
250 254 return attr
251 255
252 256 def GetNumberRows(self):
253 257 return len(self.items)
254 258
255 259 def GetNumberCols(self):
256 260 return len(self._displayattrs)
257 261
258 262 def GetColLabelValue(self, col):
259 263 if col < len(self._displayattrs):
260 264 return self._displayattrs[col].name()
261 265 else:
262 266 return ""
263 267
264 268 def GetRowLabelValue(self, row):
265 269 return str(row)
266 270
267 271 def IsEmptyCell(self, row, col):
268 272 return False
269 273
270 274 def _append(self, item):
271 275 self.items.append(item)
272 276 # Nothing to do if the set of attributes has been fixed by the user
273 277 if not self.attrs:
274 278 for attr in ipipe.xattrs(item):
275 279 attr = ipipe.upgradexattr(attr)
276 280 if attr not in self._displayattrset:
277 281 self._displayattrs.append(attr)
278 282 self._displayattrset.add(attr)
279 283
280 284 def _fetch(self, count):
281 285 # Try to fill ``self.items`` with at least ``count`` objects.
282 286 have = len(self.items)
283 287 while self.iterator is not None and have < count:
284 288 try:
285 289 item = self.iterator.next()
286 290 except StopIteration:
287 291 self.iterator = None
288 292 break
289 293 except (KeyboardInterrupt, SystemExit):
290 294 raise
291 295 except Exception, exc:
292 296 have += 1
293 297 self._append(exc)
294 298 self.iterator = None
295 299 break
296 300 else:
297 301 have += 1
298 302 self._append(item)
299 303
300 304 def GetValue(self, row, col):
301 305 # some kind of dummy-function: does not return anything but "";
302 306 # (The value isn't use anyway)
303 307 # its main task is to trigger the fetch of new objects
304 308 sizing_needed = False
305 309 had_cols = len(self._displayattrs)
306 310 had_rows = len(self.items)
307 311 if row == had_rows - 1 and self.iterator is not None:
308 312 self._fetch(row + 20)
309 313 sizing_needed = True
310 314 have_rows = len(self.items)
311 315 have_cols = len(self._displayattrs)
312 316 if have_rows > had_rows:
313 317 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, have_rows - had_rows)
314 318 self.GetView().ProcessTableMessage(msg)
315 319 sizing_needed = True
316 320 if row >= have_rows:
317 321 return ""
318 322 if have_cols != had_cols:
319 323 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED, have_cols - had_cols)
320 324 self.GetView().ProcessTableMessage(msg)
321 325 sizing_needed = True
322 326 if sizing_needed:
323 327 self.GetView().AutoSizeColumns(False)
324 328 return ""
325 329
326 330 def SetValue(self, row, col, value):
327 331 pass
328 332
329 333 def refresh_content(self, event):
330 334 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, 0, self.GetNumberRows())
331 335 self.GetView().ProcessTableMessage(msg)
332 336 self.iterator = ipipe.xiter(self.input)
333 337 self.items = []
334 338 self.attrs = [] # _append will calculate new displayattrs
335 339 self._fetch(1) # fetch one...
336 340 if self.items:
337 341 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, 1)
338 342 self.GetView().ProcessTableMessage(msg)
339 343 self.GetValue(0, 0) # and trigger "fetch next 20"
340 344 item = self.items[0]
341 345 self.GetView().AutoSizeColumns(False)
342 346 panel = self.GetView().GetParent()
343 347 nb = panel.GetParent()
344 348 current = nb.GetSelection()
345 349 if nb.GetPage(current) == panel:
346 350 self.GetView().set_footer(item)
347 351
348 352 class IGridGrid(wx.grid.Grid):
349 353 # The actual grid
350 354 # all methods for selecting/sorting/picking/... data are implemented here
351 355 def __init__(self, panel, input, *attrs):
352 356 wx.grid.Grid.__init__(self, panel)
353 357 fontsize = 9
354 358 self.input = input
355 359 self.table = IGridTable(self.input, fontsize, *attrs)
356 360 self.SetTable(self.table, True)
357 361 self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows)
358 362 self.SetDefaultRenderer(IGridRenderer(self.table))
359 363 self.EnableEditing(False)
360 364 self.Bind(wx.EVT_KEY_DOWN, self.key_pressed)
361 365 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.cell_doubleclicked)
362 366 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.cell_leftclicked)
363 367 self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_DCLICK, self.label_doubleclicked)
364 368 self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_CLICK, self.on_label_leftclick)
365 369 self.Bind(wx.grid.EVT_GRID_RANGE_SELECT, self._on_selected_range)
366 370 self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self._on_selected_cell)
367 371 self.current_selection = set()
368 372 self.maxchars = 200
369 373
370 374 def on_label_leftclick(self, event):
371 375 event.Skip()
372 376
373 377 def error_output(self, text):
374 378 wx.Bell()
375 379 frame = self.GetParent().GetParent().GetParent()
376 380 frame.SetStatusText(str(text))
377 381
378 382 def _on_selected_range(self, event):
379 383 # Internal update to the selection tracking lists
380 384 if event.Selecting():
381 385 # adding to the list...
382 386 self.current_selection.update(xrange(event.GetTopRow(), event.GetBottomRow()+1))
383 387 else:
384 388 # removal from list
385 389 for index in xrange(event.GetTopRow(), event.GetBottomRow()+1):
386 390 self.current_selection.discard(index)
387 391 event.Skip()
388 392
389 393 def _on_selected_cell(self, event):
390 394 # Internal update to the selection tracking list
391 395 self.current_selection = set([event.GetRow()])
392 396 event.Skip()
393 397
394 398 def sort(self, key, reverse=False):
395 399 """
396 400 Sort the current list of items using the key function ``key``. If
397 401 ``reverse`` is true the sort order is reversed.
398 402 """
399 403 row = self.GetGridCursorRow()
400 404 col = self.GetGridCursorCol()
401 405 curitem = self.table.items[row] # Remember where the cursor is now
402 406 # Sort items
403 407 def realkey(item):
404 408 try:
405 409 return key(item)
406 410 except (KeyboardInterrupt, SystemExit):
407 411 raise
408 412 except Exception:
409 413 return None
410 414 try:
411 415 self.table.items = ipipe.deque(sorted(self.table.items, key=realkey, reverse=reverse))
412 416 except TypeError, exc:
413 417 self.error_output("Exception encountered: %s" % exc)
414 418 return
415 419 # Find out where the object under the cursor went
416 420 for (i, item) in enumerate(self.table.items):
417 421 if item is curitem:
418 422 self.SetGridCursor(i,col)
419 423 self.MakeCellVisible(i,col)
420 424 self.Refresh()
421 425
422 426 def sortattrasc(self):
423 427 """
424 428 Sort in ascending order; sorting criteria is the current attribute
425 429 """
426 430 col = self.GetGridCursorCol()
427 431 attr = self.table._displayattrs[col]
428 432 frame = self.GetParent().GetParent().GetParent()
429 433 if attr is ipipe.noitem:
430 434 self.error_output("no column under cursor")
431 435 return
432 436 frame.SetStatusText("sort by %s (ascending)" % attr.name())
433 437 def key(item):
434 438 try:
435 439 return attr.value(item)
436 440 except (KeyboardInterrupt, SystemExit):
437 441 raise
438 442 except Exception:
439 443 return None
440 444 self.sort(key)
441 445
442 446 def sortattrdesc(self):
443 447 """
444 448 Sort in descending order; sorting criteria is the current attribute
445 449 """
446 450 col = self.GetGridCursorCol()
447 451 attr = self.table._displayattrs[col]
448 452 frame = self.GetParent().GetParent().GetParent()
449 453 if attr is ipipe.noitem:
450 454 self.error_output("no column under cursor")
451 455 return
452 456 frame.SetStatusText("sort by %s (descending)" % attr.name())
453 457 def key(item):
454 458 try:
455 459 return attr.value(item)
456 460 except (KeyboardInterrupt, SystemExit):
457 461 raise
458 462 except Exception:
459 463 return None
460 464 self.sort(key, reverse=True)
461 465
462 466 def label_doubleclicked(self, event):
463 467 row = event.GetRow()
464 468 col = event.GetCol()
465 469 if col == -1:
466 470 self.enter(row)
467 471
468 472 def _getvalue(self, row, col):
469 473 """
470 474 Gets the text which is displayed at ``(row, col)``
471 475 """
472 476 try:
473 477 value = self.table._displayattrs[col].value(self.table.items[row])
474 478 (align, width, text) = ipipe.xformat(value, "cell", self.maxchars)
475 479 except IndexError:
476 480 raise IndexError
477 481 except Exception, exc:
478 482 (align, width, text) = ipipe.xformat(exc, "cell", self.maxchars)
479 483 return text
480 484
481 485 def searchexpression(self, searchexp, startrow=None, search_forward=True ):
482 486 """
483 487 Find by expression
484 488 """
485 489 frame = self.GetParent().GetParent().GetParent()
486 490 if searchexp:
487 491 if search_forward:
488 492 if not startrow:
489 493 row = self.GetGridCursorRow()+1
490 494 else:
491 495 row = startrow + 1
492 496 while True:
493 497 try:
494 498 foo = self.table.GetValue(row, 0)
495 499 item = self.table.items[row]
496 500 try:
497 501 globals = ipipe.getglobals(None)
498 502 if eval(searchexp, globals, ipipe.AttrNamespace(item)):
499 503 self.SetGridCursor(row, 0) # found something
500 504 self.MakeCellVisible(row, 0)
501 505 break
502 506 except (KeyboardInterrupt, SystemExit):
503 507 raise
504 508 except Exception, exc:
505 509 frame.SetStatusText(str(exc))
506 510 wx.Bell()
507 511 break # break on error
508 512 except IndexError:
509 513 return
510 514 row += 1
511 515 else:
512 516 if not startrow:
513 517 row = self.GetGridCursorRow() - 1
514 518 else:
515 519 row = startrow - 1
516 520 while True:
517 521 try:
518 522 foo = self.table.GetValue(row, 0)
519 523 item = self.table.items[row]
520 524 try:
521 525 globals = ipipe.getglobals(None)
522 526 if eval(searchexp, globals, ipipe.AttrNamespace(item)):
523 527 self.SetGridCursor(row, 0) # found something
524 528 self.MakeCellVisible(row, 0)
525 529 break
526 530 except (KeyboardInterrupt, SystemExit):
527 531 raise
528 532 except Exception, exc:
529 533 frame.SetStatusText(str(exc))
530 534 wx.Bell()
531 535 break # break on error
532 536 except IndexError:
533 537 return
534 538 row -= 1
535 539
536 540
537 541 def search(self, searchtext, startrow=None, startcol=None, search_forward=True):
538 542 """
539 543 search for ``searchtext``, starting in ``(startrow, startcol)``;
540 544 if ``search_forward`` is true the direction is "forward"
541 545 """
542 546 searchtext = searchtext.lower()
543 547 if search_forward:
544 548 if startrow is not None and startcol is not None:
545 549 row = startrow
546 550 else:
547 551 startcol = self.GetGridCursorCol() + 1
548 552 row = self.GetGridCursorRow()
549 553 if startcol >= self.GetNumberCols():
550 554 startcol = 0
551 555 row += 1
552 556 while True:
553 557 for col in xrange(startcol, self.table.GetNumberCols()):
554 558 try:
555 559 foo = self.table.GetValue(row, col)
556 560 text = self._getvalue(row, col)
557 561 if searchtext in text.string().lower():
558 562 self.SetGridCursor(row, col)
559 563 self.MakeCellVisible(row, col)
560 564 return
561 565 except IndexError:
562 566 return
563 567 startcol = 0
564 568 row += 1
565 569 else:
566 570 if startrow is not None and startcol is not None:
567 571 row = startrow
568 572 else:
569 573 startcol = self.GetGridCursorCol() - 1
570 574 row = self.GetGridCursorRow()
571 575 if startcol < 0:
572 576 startcol = self.GetNumberCols() - 1
573 577 row -= 1
574 578 while True:
575 579 for col in xrange(startcol, -1, -1):
576 580 try:
577 581 foo = self.table.GetValue(row, col)
578 582 text = self._getvalue(row, col)
579 583 if searchtext in text.string().lower():
580 584 self.SetGridCursor(row, col)
581 585 self.MakeCellVisible(row, col)
582 586 return
583 587 except IndexError:
584 588 return
585 589 startcol = self.table.GetNumberCols()-1
586 590 row -= 1
587 591
588 592 def key_pressed(self, event):
589 593 """
590 594 Maps pressed keys to functions
591 595 """
592 596 frame = self.GetParent().GetParent().GetParent()
593 597 frame.SetStatusText("")
594 598 sh = event.ShiftDown()
595 599 ctrl = event.ControlDown()
596 600
597 601 keycode = event.GetKeyCode()
598 602 if keycode == ord("P"):
599 603 row = self.GetGridCursorRow()
600 604 if sh:
601 605 col = self.GetGridCursorCol()
602 606 self.pickattr(row, col)
603 607 else:
604 608 self.pick(row)
605 609 elif keycode == ord("M"):
606 610 if ctrl:
607 611 col = self.GetGridCursorCol()
608 612 self.pickrowsattr(sorted(self.current_selection), col)
609 613 else:
610 614 self.pickrows(sorted(self.current_selection))
611 615 elif keycode in (wx.WXK_BACK, wx.WXK_DELETE, ord("X")) and not (ctrl or sh):
612 616 self.delete_current_notebook()
613 617 elif keycode in (ord("E"), ord("\r")):
614 618 row = self.GetGridCursorRow()
615 619 if sh:
616 620 col = self.GetGridCursorCol()
617 621 self.enterattr(row, col)
618 622 else:
619 623 self.enter(row)
620 624 elif keycode == ord("E") and ctrl:
621 625 row = self.GetGridCursorRow()
622 626 self.SetGridCursor(row, self.GetNumberCols()-1)
623 627 elif keycode == wx.WXK_HOME or (keycode == ord("A") and ctrl):
624 628 row = self.GetGridCursorRow()
625 629 self.SetGridCursor(row, 0)
626 630 elif keycode == ord("C") and sh:
627 631 col = self.GetGridCursorCol()
628 632 attr = self.table._displayattrs[col]
629 633 result = []
630 634 for i in xrange(self.GetNumberRows()):
631 635 result.append(self.table._displayattrs[col].value(self.table.items[i]))
632 636 self.quit(result)
633 637 elif keycode in (wx.WXK_ESCAPE, ord("Q")) and not (ctrl or sh):
634 638 self.quit()
635 639 elif keycode == ord("<"):
636 640 row = self.GetGridCursorRow()
637 641 col = self.GetGridCursorCol()
638 642 if not event.ShiftDown():
639 643 newcol = col - 1
640 644 if newcol >= 0:
641 645 self.SetGridCursor(row, col - 1)
642 646 else:
643 647 newcol = col + 1
644 648 if newcol < self.GetNumberCols():
645 649 self.SetGridCursor(row, col + 1)
646 650 elif keycode == ord("D"):
647 651 col = self.GetGridCursorCol()
648 652 row = self.GetGridCursorRow()
649 653 if not sh:
650 654 self.detail(row, col)
651 655 else:
652 656 self.detail_attr(row, col)
653 657 elif keycode == ord("F") and ctrl:
654 658 if sh:
655 659 frame.enter_searchexpression(event)
656 660 else:
657 661 frame.enter_searchtext(event)
658 662 elif keycode == wx.WXK_F3:
659 663 if sh:
660 664 frame.find_previous(event)
661 665 else:
662 666 frame.find_next(event)
663 667 elif keycode == ord("V"):
664 668 if sh:
665 669 self.sortattrdesc()
666 670 else:
667 671 self.sortattrasc()
668 672 elif keycode == wx.WXK_DOWN:
669 673 row = self.GetGridCursorRow()
670 674 try:
671 675 item = self.table.items[row+1]
672 676 except IndexError:
673 677 item = self.table.items[row]
674 678 self.set_footer(item)
675 679 event.Skip()
676 680 elif keycode == wx.WXK_UP:
677 681 row = self.GetGridCursorRow()
678 682 if row >= 1:
679 683 item = self.table.items[row-1]
680 684 else:
681 685 item = self.table.items[row]
682 686 self.set_footer(item)
683 687 event.Skip()
684 688 elif keycode == wx.WXK_RIGHT:
685 689 row = self.GetGridCursorRow()
686 690 item = self.table.items[row]
687 691 self.set_footer(item)
688 692 event.Skip()
689 693 elif keycode == wx.WXK_LEFT:
690 694 row = self.GetGridCursorRow()
691 695 item = self.table.items[row]
692 696 self.set_footer(item)
693 697 event.Skip()
694 698 elif keycode == ord("R") or keycode == wx.WXK_F5:
695 699 self.table.refresh_content(event)
696 700 elif keycode == ord("I"):
697 701 row = self.GetGridCursorRow()
698 702 if not sh:
699 703 self.pickinput(row)
700 704 else:
701 705 col = self.GetGridCursorCol()
702 706 self.pickinputattr(row, col)
703 707 else:
704 708 event.Skip()
705 709
706 710 def delete_current_notebook(self):
707 711 """
708 712 deletes the current notebook tab
709 713 """
710 714 panel = self.GetParent()
711 715 nb = panel.GetParent()
712 716 current = nb.GetSelection()
713 717 count = nb.GetPageCount()
714 718 if count > 1:
715 719 for i in xrange(count-1, current-1, -1):
716 720 nb.DeletePage(i)
717 721 nb.GetCurrentPage().grid.SetFocus()
718 722 else:
719 723 frame = nb.GetParent()
720 724 frame.SetStatusText("This is the last level!")
721 725
722 726 def _doenter(self, value, *attrs):
723 727 """
724 728 "enter" a special item resulting in a new notebook tab
725 729 """
726 730 panel = self.GetParent()
727 731 nb = panel.GetParent()
728 732 frame = nb.GetParent()
729 733 current = nb.GetSelection()
730 734 count = nb.GetPageCount()
731 735 try: # if we want to enter something non-iterable, e.g. a function
732 736 if current + 1 == count and value is not self.input: # we have an event in the last tab
733 737 frame._add_notebook(value, *attrs)
734 738 elif value != self.input: # we have to delete all tabs newer than [panel] first
735 739 for i in xrange(count-1, current, -1): # some tabs don't close if we don't close in *reverse* order
736 740 nb.DeletePage(i)
737 741 frame._add_notebook(value)
738 742 except TypeError, exc:
739 743 if exc.__class__.__module__ == "exceptions":
740 744 msg = "%s: %s" % (exc.__class__.__name__, exc)
741 745 else:
742 746 msg = "%s.%s: %s" % (exc.__class__.__module__, exc.__class__.__name__, exc)
743 747 frame.SetStatusText(msg)
744 748
745 749 def enterattr(self, row, col):
746 750 try:
747 751 attr = self.table._displayattrs[col]
748 752 value = attr.value(self.table.items[row])
749 753 except Exception, exc:
750 754 self.error_output(str(exc))
751 755 else:
752 756 self._doenter(value)
753 757
754 758 def set_footer(self, item):
755 759 frame = self.GetParent().GetParent().GetParent()
756 760 frame.SetStatusText(" ".join([str(text) for (style, text) in ipipe.xformat(item, "footer", 20)[2]]), 0)
757 761
758 762 def enter(self, row):
759 763 try:
760 764 value = self.table.items[row]
761 765 except Exception, exc:
762 766 self.error_output(str(exc))
763 767 else:
764 768 self._doenter(value)
765 769
766 770 def detail(self, row, col):
767 771 """
768 772 shows a detail-view of the current cell
769 773 """
770 774 try:
771 775 attr = self.table._displayattrs[col]
772 776 item = self.table.items[row]
773 777 except Exception, exc:
774 778 self.error_output(str(exc))
775 779 else:
776 780 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
777 781 self._doenter(attrs)
778 782
779 783 def detail_attr(self, row, col):
780 784 try:
781 785 attr = self.table._displayattrs[col]
782 786 item = attr.value(self.table.items[row])
783 787 except Exception, exc:
784 788 self.error_output(str(exc))
785 789 else:
786 790 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
787 791 self._doenter(attrs)
788 792
789 793 def quit(self, result=None):
790 794 """
791 795 quit
792 796 """
793 797 frame = self.GetParent().GetParent().GetParent()
794 798 if frame.helpdialog:
795 799 frame.helpdialog.Destroy()
796 800 app = frame.parent
797 801 if app is not None:
798 802 app.result = result
799 803 frame.Close()
800 804 frame.Destroy()
801 805
802 806 def cell_doubleclicked(self, event):
803 807 self.enterattr(event.GetRow(), event.GetCol())
804 808 event.Skip()
805 809
806 810 def cell_leftclicked(self, event):
807 811 row = event.GetRow()
808 812 item = self.table.items[row]
809 813 self.set_footer(item)
810 814 event.Skip()
811 815
812 816 def pick(self, row):
813 817 """
814 818 pick a single row and return to the IPython prompt
815 819 """
816 820 try:
817 821 value = self.table.items[row]
818 822 except Exception, exc:
819 823 self.error_output(str(exc))
820 824 else:
821 825 self.quit(value)
822 826
823 827 def pickinput(self, row):
824 828 try:
825 829 value = self.table.items[row]
826 830 except Exception, exc:
827 831 self.error_output(str(exc))
828 832 else:
829 833 api = ipapi.get()
830 834 api.set_next_input(str(value))
831 835 self.quit(value)
832 836
833 837 def pickinputattr(self, row, col):
834 838 try:
835 839 attr = self.table._displayattrs[col]
836 840 value = attr.value(self.table.items[row])
837 841 except Exception, exc:
838 842 self.error_output(str(exc))
839 843 else:
840 844 api = ipapi.get()
841 845 api.set_next_input(str(value))
842 846 self.quit(value)
843 847
844 848 def pickrows(self, rows):
845 849 """
846 850 pick multiple rows and return to the IPython prompt
847 851 """
848 852 try:
849 853 value = [self.table.items[row] for row in rows]
850 854 except Exception, exc:
851 855 self.error_output(str(exc))
852 856 else:
853 857 self.quit(value)
854 858
855 859 def pickrowsattr(self, rows, col):
856 860 """"
857 861 pick one column from multiple rows
858 862 """
859 863 values = []
860 864 try:
861 865 attr = self.table._displayattrs[col]
862 866 for row in rows:
863 867 try:
864 868 values.append(attr.value(self.table.items[row]))
865 869 except (SystemExit, KeyboardInterrupt):
866 870 raise
867 871 except Exception:
868 872 raise #pass
869 873 except Exception, exc:
870 874 self.error_output(str(exc))
871 875 else:
872 876 self.quit(values)
873 877
874 878 def pickattr(self, row, col):
875 879 try:
876 880 attr = self.table._displayattrs[col]
877 881 value = attr.value(self.table.items[row])
878 882 except Exception, exc:
879 883 self.error_output(str(exc))
880 884 else:
881 885 self.quit(value)
882 886
883 887
884 888 class IGridPanel(wx.Panel):
885 889 # Each IGridPanel contains an IGridGrid
886 890 def __init__(self, parent, input, *attrs):
887 891 wx.Panel.__init__(self, parent, -1)
888 892 self.grid = IGridGrid(self, input, *attrs)
889 893 self.grid.FitInside()
890 894 sizer = wx.BoxSizer(wx.VERTICAL)
891 895 sizer.Add(self.grid, proportion=1, flag=wx.EXPAND | wx.ALL, border=10)
892 896 self.SetSizer(sizer)
893 897 sizer.Fit(self)
894 898 sizer.SetSizeHints(self)
895 899
896 900
897 901 class IGridHTMLHelp(wx.Frame):
898 902 def __init__(self, parent, title, size):
899 903 wx.Frame.__init__(self, parent, -1, title, size=size)
900 904 html = wx.html.HtmlWindow(self)
901 905 if "gtk2" in wx.PlatformInfo:
902 906 html.SetStandardFonts()
903 907 html.SetPage(help)
904 908
905 909
906 910 class IGridFrame(wx.Frame):
907 911 maxtitlelen = 30
908 912
909 913 def __init__(self, parent, input):
910 914 title = " ".join([str(text) for (style, text) in ipipe.xformat(input, "header", 20)[2]])
911 915 wx.Frame.__init__(self, None, title=title, size=(640, 480))
912 916 self.menubar = wx.MenuBar()
913 917 self.menucounter = 100
914 918 self.m_help = wx.Menu()
915 919 self.m_search = wx.Menu()
916 920 self.m_sort = wx.Menu()
917 921 self.m_refresh = wx.Menu()
918 922 self.notebook = wx.Notebook(self, -1, style=0)
919 923 self.statusbar = self.CreateStatusBar(1, wx.ST_SIZEGRIP)
920 924 self.statusbar.SetFieldsCount(2)
921 925 self.SetStatusWidths([-1, 200])
922 926 self.parent = parent
923 927 self._add_notebook(input)
924 928 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
925 929 self.makemenu(self.m_sort, "&Sort (asc)\tV", "Sort ascending", self.sortasc)
926 930 self.makemenu(self.m_sort, "Sort (&desc)\tShift-V", "Sort descending", self.sortdesc)
927 931 self.makemenu(self.m_help, "&Help\tF1", "Help", self.display_help)
928 932 # self.makemenu(self.m_help, "&Show help in browser", "Show help in browser", self.display_help_in_browser)
929 933 self.makemenu(self.m_search, "&Find text\tCTRL-F", "Find text", self.enter_searchtext)
930 934 self.makemenu(self.m_search, "Find by &expression\tCTRL-Shift-F", "Find by expression", self.enter_searchexpression)
931 935 self.makemenu(self.m_search, "Find &next\tF3", "Find next", self.find_next)
932 936 self.makemenu(self.m_search, "Find &previous\tShift-F3", "Find previous", self.find_previous)
933 937 self.makemenu(self.m_refresh, "&Refresh once \tF5", "Refresh once", self.refresh_once)
934 938 self.makemenu(self.m_refresh, "Refresh every &1s", "Refresh every second", self.refresh_every_second)
935 939 self.makemenu(self.m_refresh, "Refresh every &X seconds", "Refresh every X seconds", self.refresh_interval)
936 940 self.makemenu(self.m_refresh, "&Stop all refresh timers", "Stop refresh timers", self.stop_refresh)
937 941 self.menubar.Append(self.m_search, "&Find")
938 942 self.menubar.Append(self.m_sort, "&Sort")
939 943 self.menubar.Append(self.m_refresh, "&Refresh")
940 944 self.menubar.Append(self.m_help, "&Help")
941 945 self.SetMenuBar(self.menubar)
942 946 self.searchtext = ""
943 947 self.searchexpression = ""
944 948 self.helpdialog = None
945 949 self.refresh_interval = 1000
946 950 self.SetStatusText("Refreshing inactive", 1)
947 951
948 952 def refresh_once(self, event):
949 953 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
950 954 table.refresh_content(event)
951 955
952 956 def refresh_interval(self, event):
953 957 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
954 958 dlg = wx.TextEntryDialog(self, "Enter refresh interval (milliseconds):", "Refresh timer:", defaultValue=str(self.refresh_interval))
955 959 if dlg.ShowModal() == wx.ID_OK:
956 960 try:
957 961 milliseconds = int(dlg.GetValue())
958 962 except ValueError, exc:
959 963 self.SetStatusText(str(exc))
960 964 else:
961 965 table.timer.Start(milliseconds=milliseconds, oneShot=False)
962 966 self.SetStatusText("Refresh timer set to %s ms" % milliseconds)
963 967 self.SetStatusText("Refresh interval: %s ms" % milliseconds, 1)
964 968 self.refresh_interval = milliseconds
965 969 dlg.Destroy()
966 970
967 971 def stop_refresh(self, event):
968 972 for i in xrange(self.notebook.GetPageCount()):
969 973 nb = self.notebook.GetPage(i)
970 974 nb.grid.table.timer.Stop()
971 975 self.SetStatusText("Refreshing inactive", 1)
972 976
973 977 def refresh_every_second(self, event):
974 978 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
975 979 table.timer.Start(milliseconds=1000, oneShot=False)
976 980 self.SetStatusText("Refresh interval: 1000 ms", 1)
977 981
978 982 def sortasc(self, event):
979 983 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
980 984 grid.sortattrasc()
981 985
982 986 def sortdesc(self, event):
983 987 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
984 988 grid.sortattrdesc()
985 989
986 990 def find_previous(self, event):
987 991 """
988 992 find previous occurrences
989 993 """
990 994 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
991 995 if self.searchtext:
992 996 row = grid.GetGridCursorRow()
993 997 col = grid.GetGridCursorCol()
994 998 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
995 999 if col-1 >= 0:
996 1000 grid.search(self.searchtext, row, col-1, False)
997 1001 else:
998 1002 grid.search(self.searchtext, row-1, grid.table.GetNumberCols()-1, False)
999 1003 elif self.searchexpression:
1000 1004 self.SetStatusText("Search mode: expression; looking for %s" % repr(self.searchexpression)[2:-1])
1001 1005 grid.searchexpression(searchexp=self.searchexpression, search_forward=False)
1002 1006 else:
1003 1007 self.SetStatusText("No search yet: please enter search-text or -expression")
1004 1008
1005 1009 def find_next(self, event):
1006 1010 """
1007 1011 find the next occurrence
1008 1012 """
1009 1013 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
1010 1014 if self.searchtext != "":
1011 1015 row = grid.GetGridCursorRow()
1012 1016 col = grid.GetGridCursorCol()
1013 1017 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
1014 1018 if col+1 < grid.table.GetNumberCols():
1015 1019 grid.search(self.searchtext, row, col+1)
1016 1020 else:
1017 1021 grid.search(self.searchtext, row+1, 0)
1018 1022 elif self.searchexpression != "":
1019 1023 self.SetStatusText('Search mode: expression; looking for %s' % repr(self.searchexpression)[2:-1])
1020 1024 grid.searchexpression(searchexp=self.searchexpression)
1021 1025 else:
1022 1026 self.SetStatusText("No search yet: please enter search-text or -expression")
1023 1027
1024 1028 def display_help(self, event):
1025 1029 """
1026 1030 Display a help dialog
1027 1031 """
1028 1032 if self.helpdialog:
1029 1033 self.helpdialog.Destroy()
1030 1034 self.helpdialog = IGridHTMLHelp(None, title="Help", size=wx.Size(600,400))
1031 1035 self.helpdialog.Show()
1032 1036
1033 1037 def display_help_in_browser(self, event):
1034 1038 """
1035 1039 Show the help-HTML in a browser (as a ``HtmlWindow`` does not understand
1036 1040 CSS this looks better)
1037 1041 """
1038 1042 filename = urllib.pathname2url(os.path.abspath(os.path.join(os.path.dirname(__file__), "igrid_help.html")))
1039 1043 if not filename.startswith("file"):
1040 1044 filename = "file:" + filename
1041 1045 webbrowser.open(filename, new=1, autoraise=True)
1042 1046
1043 1047 def enter_searchexpression(self, event):
1044 1048 dlg = wx.TextEntryDialog(self, "Find:", "Find matching expression:", defaultValue=self.searchexpression)
1045 1049 if dlg.ShowModal() == wx.ID_OK:
1046 1050 self.searchexpression = dlg.GetValue()
1047 1051 self.searchtext = ""
1048 1052 self.SetStatusText('Search mode: expression; looking for %s' % repr(self.searchexpression)[2:-1])
1049 1053 self.notebook.GetPage(self.notebook.GetSelection()).grid.searchexpression(self.searchexpression)
1050 1054 dlg.Destroy()
1051 1055
1052 1056 def makemenu(self, menu, label, help, cmd):
1053 1057 menu.Append(self.menucounter, label, help)
1054 1058 self.Bind(wx.EVT_MENU, cmd, id=self.menucounter)
1055 1059 self.menucounter += 1
1056 1060
1057 1061 def _add_notebook(self, input, *attrs):
1058 1062 # Adds another notebook which has the starting object ``input``
1059 1063 panel = IGridPanel(self.notebook, input, *attrs)
1060 1064 text = str(ipipe.xformat(input, "header", self.maxtitlelen)[2])
1061 1065 if len(text) >= self.maxtitlelen:
1062 1066 text = text[:self.maxtitlelen].rstrip(".") + "..."
1063 1067 self.notebook.AddPage(panel, text, True)
1064 1068 panel.grid.SetFocus()
1065 1069 self.Layout()
1066 1070
1067 1071 def OnCloseWindow(self, event):
1068 1072 self.Destroy()
1069 1073
1070 1074 def enter_searchtext(self, event):
1071 1075 # Displays a dialog asking for the searchtext
1072 1076 dlg = wx.TextEntryDialog(self, "Find:", "Find in list", defaultValue=self.searchtext)
1073 1077 if dlg.ShowModal() == wx.ID_OK:
1074 1078 self.searchtext = dlg.GetValue()
1075 1079 self.searchexpression = ""
1076 1080 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
1077 1081 self.notebook.GetPage(self.notebook.GetSelection()).grid.search(self.searchtext)
1078 1082 dlg.Destroy()
1079 1083
1080 1084
1081 1085 class App(wx.App):
1082 1086 def __init__(self, input):
1083 1087 self.input = input
1084 1088 self.result = None # Result to be returned to IPython. Set by quit().
1085 1089 wx.App.__init__(self)
1086 1090
1087 1091 def OnInit(self):
1088 1092 frame = IGridFrame(self, self.input)
1089 1093 frame.Show()
1090 1094 self.SetTopWindow(frame)
1091 1095 frame.Raise()
1092 1096 return True
1093 1097
1094 1098
1095 1099 class igrid(ipipe.Display):
1096 1100 """
1097 1101 This is a wx-based display object that can be used instead of ``ibrowse``
1098 1102 (which is curses-based) or ``idump`` (which simply does a print).
1099 1103 """
1100 1104 if wx.VERSION < (2, 7):
1101 1105 def display(self):
1102 1106 try:
1103 1107 # Try to create a "standalone" from. If this works we're probably
1104 1108 # running with -wthread.
1105 1109 # Note that this sets the parent of the frame to None, but we can't
1106 1110 # pass a result object back to the shell anyway.
1107 1111 frame = IGridFrame(None, self.input)
1108 1112 frame.Show()
1109 1113 frame.Raise()
1110 1114 except wx.PyNoAppError:
1111 1115 # There's no wx application yet => create one.
1112 1116 app = App(self.input)
1113 1117 app.MainLoop()
1114 1118 return app.result
1115 1119 else:
1116 1120 # With wx 2.7 it gets simpler.
1117 1121 def display(self):
1118 1122 app = App(self.input)
1119 1123 app.MainLoop()
1120 1124 return app.result
1121 1125
@@ -1,352 +1,357 b''
1 1 #!/usr/bin/env python
2 2
3 3 """ Implementations for various useful completers
4 4
5 5 See Extensions/ipy_stock_completers.py on examples of how to enable a completer,
6 6 but the basic idea is to do:
7 7
8 8 ip.set_hook('complete_command', svn_completer, str_key = 'svn')
9 9
10 10 """
11 11 import IPython.ipapi
12 12 import glob,os,shlex,sys
13 13 import inspect
14 14 from time import time
15 15 ip = IPython.ipapi.get()
16 16
17 try:
18 set
19 except:
20 from sets import Set as set
21
17 22 TIMEOUT_STORAGE = 3 #Time in seconds after which the rootmodules will be stored
18 23 TIMEOUT_GIVEUP = 20 #Time in seconds after which we give up
19 24
20 25 def quick_completer(cmd, completions):
21 26 """ Easily create a trivial completer for a command.
22 27
23 28 Takes either a list of completions, or all completions in string
24 29 (that will be split on whitespace)
25 30
26 31 Example::
27 32
28 33 [d:\ipython]|1> import ipy_completers
29 34 [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz'])
30 35 [d:\ipython]|3> foo b<TAB>
31 36 bar baz
32 37 [d:\ipython]|3> foo ba
33 38 """
34 39 if isinstance(completions, basestring):
35 40
36 41 completions = completions.split()
37 42 def do_complete(self,event):
38 43 return completions
39 44
40 45 ip.set_hook('complete_command',do_complete, str_key = cmd)
41 46
42 47 def getRootModules():
43 48 """
44 49 Returns a list containing the names of all the modules available in the
45 50 folders of the pythonpath.
46 51 """
47 52 modules = []
48 53 if ip.db.has_key('rootmodules'):
49 54 return ip.db['rootmodules']
50 55 t = time()
51 56 store = False
52 57 for path in sys.path:
53 58 modules += moduleList(path)
54 59 if time() - t >= TIMEOUT_STORAGE and not store:
55 60 store = True
56 61 print "\nCaching the list of root modules, please wait!"
57 62 print "(This will only be done once - type '%rehashx' to " + \
58 63 "reset cache!)"
59 64 print
60 65 if time() - t > TIMEOUT_GIVEUP:
61 66 print "This is taking too long, we give up."
62 67 print
63 68 ip.db['rootmodules'] = []
64 69 return []
65 70
66 71 modules += sys.builtin_module_names
67 72
68 73 modules = list(set(modules))
69 74 if '__init__' in modules:
70 75 modules.remove('__init__')
71 76 modules = list(set(modules))
72 77 if store:
73 78 ip.db['rootmodules'] = modules
74 79 return modules
75 80
76 81 def moduleList(path):
77 82 """
78 83 Return the list containing the names of the modules available in the given
79 84 folder.
80 85 """
81 86
82 87 if os.path.isdir(path):
83 88 folder_list = os.listdir(path)
84 89 else:
85 90 folder_list = []
86 91 #folder_list = glob.glob(os.path.join(path,'*'))
87 92 folder_list = [p for p in folder_list \
88 93 if os.path.exists(os.path.join(path, p,'__init__.py'))\
89 94 or p[-3:] in ('.py','.so')\
90 95 or p[-4:] in ('.pyc','.pyo')]
91 96
92 97 folder_list = [os.path.basename(p).split('.')[0] for p in folder_list]
93 98 return folder_list
94 99
95 100 def moduleCompletion(line):
96 101 """
97 102 Returns a list containing the completion possibilities for an import line.
98 103 The line looks like this :
99 104 'import xml.d'
100 105 'from xml.dom import'
101 106 """
102 107 def tryImport(mod, only_modules=False):
103 108 def isImportable(module, attr):
104 109 if only_modules:
105 110 return inspect.ismodule(getattr(module, attr))
106 111 else:
107 112 return not(attr[:2] == '__' and attr[-2:] == '__')
108 113 try:
109 114 m = __import__(mod)
110 115 except:
111 116 return []
112 117 mods = mod.split('.')
113 118 for module in mods[1:]:
114 119 m = getattr(m,module)
115 120 if (not hasattr(m, '__file__')) or (not only_modules) or\
116 121 (hasattr(m, '__file__') and '__init__' in m.__file__):
117 122 completion_list = [attr for attr in dir(m) if isImportable(m, attr)]
118 123 completion_list.extend(getattr(m,'__all__',[]))
119 124 if hasattr(m, '__file__') and '__init__' in m.__file__:
120 125 completion_list.extend(moduleList(os.path.dirname(m.__file__)))
121 126 completion_list = list(set(completion_list))
122 127 if '__init__' in completion_list:
123 128 completion_list.remove('__init__')
124 129 return completion_list
125 130
126 131 words = line.split(' ')
127 132 if len(words) == 3 and words[0] == 'from':
128 133 return ['import ']
129 134 if len(words) < 3 and (words[0] in ['import','from']) :
130 135 if len(words) == 1:
131 136 return getRootModules()
132 137 mod = words[1].split('.')
133 138 if len(mod) < 2:
134 139 return getRootModules()
135 140 completion_list = tryImport('.'.join(mod[:-1]), True)
136 141 completion_list = ['.'.join(mod[:-1] + [el]) for el in completion_list]
137 142 return completion_list
138 143 if len(words) >= 3 and words[0] == 'from':
139 144 mod = words[1]
140 145 return tryImport(mod)
141 146
142 147 def vcs_completer(commands, event):
143 148 """ utility to make writing typical version control app completers easier
144 149
145 150 VCS command line apps typically have the format:
146 151
147 152 [sudo ]PROGNAME [help] [command] file file...
148 153
149 154 """
150 155
151 156
152 157 cmd_param = event.line.split()
153 158 if event.line.endswith(' '):
154 159 cmd_param.append('')
155 160
156 161 if cmd_param[0] == 'sudo':
157 162 cmd_param = cmd_param[1:]
158 163
159 164 if len(cmd_param) == 2 or 'help' in cmd_param:
160 165 return commands.split()
161 166
162 167 return ip.IP.Completer.file_matches(event.symbol)
163 168
164 169
165 170 pkg_cache = None
166 171
167 172 def module_completer(self,event):
168 173 """ Give completions after user has typed 'import ...' or 'from ...'"""
169 174
170 175 # This works in all versions of python. While 2.5 has
171 176 # pkgutil.walk_packages(), that particular routine is fairly dangerous,
172 177 # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full
173 178 # of possibly problematic side effects.
174 179 # This search the folders in the sys.path for available modules.
175 180
176 181 return moduleCompletion(event.line)
177 182
178 183
179 184 svn_commands = """\
180 185 add blame praise annotate ann cat checkout co cleanup commit ci copy
181 186 cp delete del remove rm diff di export help ? h import info list ls
182 187 lock log merge mkdir move mv rename ren propdel pdel pd propedit pedit
183 188 pe propget pget pg proplist plist pl propset pset ps resolved revert
184 189 status stat st switch sw unlock update
185 190 """
186 191
187 192 def svn_completer(self,event):
188 193 return vcs_completer(svn_commands, event)
189 194
190 195
191 196 hg_commands = """
192 197 add addremove annotate archive backout branch branches bundle cat
193 198 clone commit copy diff export grep heads help identify import incoming
194 199 init locate log manifest merge outgoing parents paths pull push
195 200 qapplied qclone qcommit qdelete qdiff qfold qguard qheader qimport
196 201 qinit qnew qnext qpop qprev qpush qrefresh qrename qrestore qsave
197 202 qselect qseries qtop qunapplied recover remove rename revert rollback
198 203 root serve showconfig status strip tag tags tip unbundle update verify
199 204 version
200 205 """
201 206
202 207 def hg_completer(self,event):
203 208 """ Completer for mercurial commands """
204 209
205 210 return vcs_completer(hg_commands, event)
206 211
207 212
208 213
209 214 bzr_commands = """
210 215 add annotate bind branch break-lock bundle-revisions cat check
211 216 checkout commit conflicts deleted diff export gannotate gbranch
212 217 gcommit gdiff help ignore ignored info init init-repository inventory
213 218 log merge missing mkdir mv nick pull push reconcile register-branch
214 219 remerge remove renames resolve revert revno root serve sign-my-commits
215 220 status testament unbind uncommit unknowns update upgrade version
216 221 version-info visualise whoami
217 222 """
218 223
219 224 def bzr_completer(self,event):
220 225 """ Completer for bazaar commands """
221 226 cmd_param = event.line.split()
222 227 if event.line.endswith(' '):
223 228 cmd_param.append('')
224 229
225 230 if len(cmd_param) > 2:
226 231 cmd = cmd_param[1]
227 232 param = cmd_param[-1]
228 233 output_file = (param == '--output=')
229 234 if cmd == 'help':
230 235 return bzr_commands.split()
231 236 elif cmd in ['bundle-revisions','conflicts',
232 237 'deleted','nick','register-branch',
233 238 'serve','unbind','upgrade','version',
234 239 'whoami'] and not output_file:
235 240 return []
236 241 else:
237 242 # the rest are probably file names
238 243 return ip.IP.Completer.file_matches(event.symbol)
239 244
240 245 return bzr_commands.split()
241 246
242 247
243 248 def shlex_split(x):
244 249 """Helper function to split lines into segments."""
245 250 #shlex.split raise exception if syntax error in sh syntax
246 251 #for example if no closing " is found. This function keeps dropping
247 252 #the last character of the line until shlex.split does not raise
248 253 #exception. Adds end of the line to the result of shlex.split
249 254 #example: %run "c:/python -> ['%run','"c:/python']
250 255 endofline=[]
251 256 while x!="":
252 257 try:
253 258 comps=shlex.split(x)
254 259 if len(endofline)>=1:
255 260 comps.append("".join(endofline))
256 261 return comps
257 262 except ValueError:
258 263 endofline=[x[-1:]]+endofline
259 264 x=x[:-1]
260 265 return ["".join(endofline)]
261 266
262 267 def runlistpy(self, event):
263 268 comps = shlex_split(event.line)
264 269 relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"")
265 270
266 271 #print "\nev=",event # dbg
267 272 #print "rp=",relpath # dbg
268 273 #print 'comps=',comps # dbg
269 274
270 275 lglob = glob.glob
271 276 isdir = os.path.isdir
272 277 if relpath.startswith('~'):
273 278 relpath = os.path.expanduser(relpath)
274 279 dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*')
275 280 if isdir(f)]
276 281
277 282 # Find if the user has already typed the first filename, after which we
278 283 # should complete on all files, since after the first one other files may
279 284 # be arguments to the input script.
280 285 #filter(
281 286 if filter(lambda f: f.endswith('.py') or f.endswith('.ipy'),comps):
282 287 pys = [f.replace('\\','/') for f in lglob('*')]
283 288 else:
284 289 pys = [f.replace('\\','/')
285 290 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy')]
286 291 return dirs + pys
287 292
288 293
289 294 def cd_completer(self, event):
290 295 relpath = event.symbol
291 296 #print event # dbg
292 297 if '-b' in event.line:
293 298 # return only bookmark completions
294 299 bkms = self.db.get('bookmarks',{})
295 300 return bkms.keys()
296 301
297 302
298 303 if event.symbol == '-':
299 304 width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
300 305 # jump in directory history by number
301 306 fmt = '-%0' + width_dh +'d [%s]'
302 307 ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
303 308 if len(ents) > 1:
304 309 return ents
305 310 return []
306 311
307 312 if relpath.startswith('~'):
308 313 relpath = os.path.expanduser(relpath).replace('\\','/')
309 314 found = []
310 315 for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
311 316 if os.path.isdir(f)]:
312 317 if ' ' in d:
313 318 # we don't want to deal with any of that, complex code
314 319 # for this is elsewhere
315 320 raise IPython.ipapi.TryNext
316 321 found.append( d )
317 322
318 323 if not found:
319 324 if os.path.isdir(relpath):
320 325 return [relpath]
321 326 raise IPython.ipapi.TryNext
322 327 return found
323 328
324 329 def apt_get_packages(prefix):
325 330 out = os.popen('apt-cache pkgnames')
326 331 for p in out:
327 332 if p.startswith(prefix):
328 333 yield p.rstrip()
329 334
330 335
331 336 apt_commands = """\
332 337 update upgrade install remove purge source build-dep dist-upgrade
333 338 dselect-upgrade clean autoclean check"""
334 339
335 340 def apt_completer(self, event):
336 341 """ Completer for apt-get (uses apt-cache internally)
337 342
338 343 """
339 344
340 345
341 346 cmd_param = event.line.split()
342 347 if event.line.endswith(' '):
343 348 cmd_param.append('')
344 349
345 350 if cmd_param[0] == 'sudo':
346 351 cmd_param = cmd_param[1:]
347 352
348 353 if len(cmd_param) == 2 or 'help' in cmd_param:
349 354 return apt_commands.split()
350 355
351 356 return list(apt_get_packages(event.symbol))
352 357
@@ -1,180 +1,184 b''
1 1 """Traits-aware tab completion.
2 2
3 3 This module provides a custom tab-completer that intelligently hides the names
4 4 that the enthought.traits library (http://code.enthought.com/traits)
5 5 automatically adds to all objects that inherit from its base HasTraits class.
6 6
7 7
8 8 Activation
9 9 ==========
10 10
11 11 To use this, put in your ~/.ipython/ipy_user_conf.py file:
12 12
13 13 from ipy_traits_completer import activate
14 14 activate([complete_threshold])
15 15
16 16 The optional complete_threshold argument is the minimal length of text you need
17 17 to type for tab-completion to list names that are automatically generated by
18 18 traits. The default value is 3. Note that at runtime, you can change this
19 19 value simply by doing:
20 20
21 21 import ipy_traits_completer
22 22 ipy_traits_completer.COMPLETE_THRESHOLD = 4
23 23
24 24
25 25 Usage
26 26 =====
27 27
28 28 The system works as follows. If t is an empty object that HasTraits, then
29 29 (assuming the threshold is at the default value of 3):
30 30
31 31 In [7]: t.ed<TAB>
32 32
33 33 doesn't show anything at all, but:
34 34
35 35 In [7]: t.edi<TAB>
36 36 t.edit_traits t.editable_traits
37 37
38 38 shows these two names that come from traits. This allows you to complete on
39 39 the traits-specific names by typing at least 3 letters from them (or whatever
40 40 you set your threshold to), but to otherwise not see them in normal completion.
41 41
42 42
43 43 Notes
44 44 =====
45 45
46 46 - This requires Python 2.4 to work (I use sets). I don't think anyone is
47 47 using traits with 2.3 anyway, so that's OK.
48 48 """
49 49
50 50 #############################################################################
51 51 # External imports
52 52 from enthought.traits import api as T
53 53
54 54 # IPython imports
55 55 from IPython.ipapi import TryNext, get as ipget
56 56 from IPython.genutils import dir2
57 try:
58 set
59 except:
60 from sets import Set as set
57 61
58 62 #############################################################################
59 63 # Module constants
60 64
61 65 # The completion threshold
62 66 # This is currently implemented as a module global, since this sytem isn't
63 67 # likely to be modified at runtime by multiple instances. If needed in the
64 68 # future, we can always make it local to the completer as a function attribute.
65 69 COMPLETE_THRESHOLD = 3
66 70
67 71 # Set of names that Traits automatically adds to ANY traits-inheriting object.
68 72 # These are the names we'll filter out.
69 73 TRAIT_NAMES = set( dir2(T.HasTraits()) ) - set( dir2(object()) )
70 74
71 75 #############################################################################
72 76 # Code begins
73 77
74 78 def trait_completer(self,event):
75 79 """A custom IPython tab-completer that is traits-aware.
76 80
77 81 It tries to hide the internal traits attributes, and reveal them only when
78 82 it can reasonably guess that the user really is after one of them.
79 83 """
80 84
81 85 #print '\nevent is:',event # dbg
82 86 symbol_parts = event.symbol.split('.')
83 87 base = '.'.join(symbol_parts[:-1])
84 88 #print 'base:',base # dbg
85 89
86 90 oinfo = self._ofind(base)
87 91 if not oinfo['found']:
88 92 raise TryNext
89 93
90 94 obj = oinfo['obj']
91 95 # OK, we got the object. See if it's traits, else punt
92 96 if not isinstance(obj,T.HasTraits):
93 97 raise TryNext
94 98
95 99 # it's a traits object, don't show the tr* attributes unless the completion
96 100 # begins with 'tr'
97 101 attrs = dir2(obj)
98 102 # Now, filter out the attributes that start with the user's request
99 103 attr_start = symbol_parts[-1]
100 104 if attr_start:
101 105 attrs = [a for a in attrs if a.startswith(attr_start)]
102 106
103 107 # Let's also respect the user's readline_omit__names setting:
104 108 omit__names = ipget().options.readline_omit__names
105 109 if omit__names == 1:
106 110 attrs = [a for a in attrs if not a.startswith('__')]
107 111 elif omit__names == 2:
108 112 attrs = [a for a in attrs if not a.startswith('_')]
109 113
110 114 #print '\nastart:<%r>' % attr_start # dbg
111 115
112 116 if len(attr_start)<COMPLETE_THRESHOLD:
113 117 attrs = list(set(attrs) - TRAIT_NAMES)
114 118
115 119 # The base of the completion, so we can form the final results list
116 120 bdot = base+'.'
117 121
118 122 tcomp = [bdot+a for a in attrs]
119 123 #print 'tcomp:',tcomp
120 124 return tcomp
121 125
122 126 def activate(complete_threshold = COMPLETE_THRESHOLD):
123 127 """Activate the Traits completer.
124 128
125 129 :Keywords:
126 130 complete_threshold : int
127 131 The minimum number of letters that a user must type in order to
128 132 activate completion of traits-private names."""
129 133
130 134 if not (isinstance(complete_threshold,int) and
131 135 complete_threshold>0):
132 136 e='complete_threshold must be a positive integer, not %r' % \
133 137 complete_threshold
134 138 raise ValueError(e)
135 139
136 140 # Set the module global
137 141 global COMPLETE_THRESHOLD
138 142 COMPLETE_THRESHOLD = complete_threshold
139 143
140 144 # Activate the traits aware completer
141 145 ip = ipget()
142 146 ip.set_hook('complete_command', trait_completer, re_key = '.*')
143 147
144 148
145 149 #############################################################################
146 150 if __name__ == '__main__':
147 151 # Testing/debugging
148 152
149 153 # A sorted list of the names we'll filter out
150 154 TNL = list(TRAIT_NAMES)
151 155 TNL.sort()
152 156
153 157 # Make a few objects for testing
154 158 class TClean(T.HasTraits): pass
155 159 class Bunch(object): pass
156 160 # A clean traits object
157 161 t = TClean()
158 162 # A nested object containing t
159 163 f = Bunch()
160 164 f.t = t
161 165 # And a naked new-style object
162 166 o = object()
163 167
164 168 ip = ipget().IP
165 169
166 170 # A few simplistic tests
167 171
168 172 # Reset the threshold to the default, in case the test is running inside an
169 173 # instance of ipython that changed it
170 174 import ipy_traits_completer
171 175 ipy_traits_completer.COMPLETE_THRESHOLD = 3
172 176
173 177 assert ip.complete('t.ed') ==[]
174 178
175 179 # For some bizarre reason, these fail on the first time I run them, but not
176 180 # afterwards. Traits does some really weird stuff at object instantiation
177 181 # time...
178 182 ta = ip.complete('t.edi')
179 183 assert ta == ['t.edit_traits', 't.editable_traits']
180 184 print 'Tests OK'
@@ -1,1990 +1,1996 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 $Id: genutils.py 2888 2007-12-12 17:20:42Z vivainio $"""
8 $Id: genutils.py 2937 2008-01-16 15:25:59Z 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 import doctest
26 26 import os
27 27 import re
28 28 import shlex
29 29 import shutil
30 30 import sys
31 31 import tempfile
32 32 import time
33 33 import types
34 34 import warnings
35 35
36 36 # Other IPython utilities
37 37 import IPython
38 38 from IPython.Itpl import Itpl,itpl,printpl
39 39 from IPython import DPyGetOpt, platutils
40 40 from IPython.generics import result_display
41 41 from path import path
42 42 if os.name == "nt":
43 43 from IPython.winconsole import get_console_size
44 44
45 try:
46 set
47 except:
48 from sets import Set as set
49
50
45 51 #****************************************************************************
46 52 # Exceptions
47 53 class Error(Exception):
48 54 """Base class for exceptions in this module."""
49 55 pass
50 56
51 57 #----------------------------------------------------------------------------
52 58 class IOStream:
53 59 def __init__(self,stream,fallback):
54 60 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
55 61 stream = fallback
56 62 self.stream = stream
57 63 self._swrite = stream.write
58 64 self.flush = stream.flush
59 65
60 66 def write(self,data):
61 67 try:
62 68 self._swrite(data)
63 69 except:
64 70 try:
65 71 # print handles some unicode issues which may trip a plain
66 72 # write() call. Attempt to emulate write() by using a
67 73 # trailing comma
68 74 print >> self.stream, data,
69 75 except:
70 76 # if we get here, something is seriously broken.
71 77 print >> sys.stderr, \
72 78 'ERROR - failed to write data to stream:', self.stream
73 79
74 80 def close(self):
75 81 pass
76 82
77 83
78 84 class IOTerm:
79 85 """ Term holds the file or file-like objects for handling I/O operations.
80 86
81 87 These are normally just sys.stdin, sys.stdout and sys.stderr but for
82 88 Windows they can can replaced to allow editing the strings before they are
83 89 displayed."""
84 90
85 91 # In the future, having IPython channel all its I/O operations through
86 92 # this class will make it easier to embed it into other environments which
87 93 # are not a normal terminal (such as a GUI-based shell)
88 94 def __init__(self,cin=None,cout=None,cerr=None):
89 95 self.cin = IOStream(cin,sys.stdin)
90 96 self.cout = IOStream(cout,sys.stdout)
91 97 self.cerr = IOStream(cerr,sys.stderr)
92 98
93 99 # Global variable to be used for all I/O
94 100 Term = IOTerm()
95 101
96 102 import IPython.rlineimpl as readline
97 103 # Remake Term to use the readline i/o facilities
98 104 if sys.platform == 'win32' and readline.have_readline:
99 105
100 106 Term = IOTerm(cout=readline._outputfile,cerr=readline._outputfile)
101 107
102 108
103 109 #****************************************************************************
104 110 # Generic warning/error printer, used by everything else
105 111 def warn(msg,level=2,exit_val=1):
106 112 """Standard warning printer. Gives formatting consistency.
107 113
108 114 Output is sent to Term.cerr (sys.stderr by default).
109 115
110 116 Options:
111 117
112 118 -level(2): allows finer control:
113 119 0 -> Do nothing, dummy function.
114 120 1 -> Print message.
115 121 2 -> Print 'WARNING:' + message. (Default level).
116 122 3 -> Print 'ERROR:' + message.
117 123 4 -> Print 'FATAL ERROR:' + message and trigger a sys.exit(exit_val).
118 124
119 125 -exit_val (1): exit value returned by sys.exit() for a level 4
120 126 warning. Ignored for all other levels."""
121 127
122 128 if level>0:
123 129 header = ['','','WARNING: ','ERROR: ','FATAL ERROR: ']
124 130 print >> Term.cerr, '%s%s' % (header[level],msg)
125 131 if level == 4:
126 132 print >> Term.cerr,'Exiting.\n'
127 133 sys.exit(exit_val)
128 134
129 135 def info(msg):
130 136 """Equivalent to warn(msg,level=1)."""
131 137
132 138 warn(msg,level=1)
133 139
134 140 def error(msg):
135 141 """Equivalent to warn(msg,level=3)."""
136 142
137 143 warn(msg,level=3)
138 144
139 145 def fatal(msg,exit_val=1):
140 146 """Equivalent to warn(msg,exit_val=exit_val,level=4)."""
141 147
142 148 warn(msg,exit_val=exit_val,level=4)
143 149
144 150 #---------------------------------------------------------------------------
145 151 # Debugging routines
146 152 #
147 153 def debugx(expr,pre_msg=''):
148 154 """Print the value of an expression from the caller's frame.
149 155
150 156 Takes an expression, evaluates it in the caller's frame and prints both
151 157 the given expression and the resulting value (as well as a debug mark
152 158 indicating the name of the calling function. The input must be of a form
153 159 suitable for eval().
154 160
155 161 An optional message can be passed, which will be prepended to the printed
156 162 expr->value pair."""
157 163
158 164 cf = sys._getframe(1)
159 165 print '[DBG:%s] %s%s -> %r' % (cf.f_code.co_name,pre_msg,expr,
160 166 eval(expr,cf.f_globals,cf.f_locals))
161 167
162 168 # deactivate it by uncommenting the following line, which makes it a no-op
163 169 #def debugx(expr,pre_msg=''): pass
164 170
165 171 #----------------------------------------------------------------------------
166 172 StringTypes = types.StringTypes
167 173
168 174 # Basic timing functionality
169 175
170 176 # If possible (Unix), use the resource module instead of time.clock()
171 177 try:
172 178 import resource
173 179 def clocku():
174 180 """clocku() -> floating point number
175 181
176 182 Return the *USER* CPU time in seconds since the start of the process.
177 183 This is done via a call to resource.getrusage, so it avoids the
178 184 wraparound problems in time.clock()."""
179 185
180 186 return resource.getrusage(resource.RUSAGE_SELF)[0]
181 187
182 188 def clocks():
183 189 """clocks() -> floating point number
184 190
185 191 Return the *SYSTEM* CPU time in seconds since the start of the process.
186 192 This is done via a call to resource.getrusage, so it avoids the
187 193 wraparound problems in time.clock()."""
188 194
189 195 return resource.getrusage(resource.RUSAGE_SELF)[1]
190 196
191 197 def clock():
192 198 """clock() -> floating point number
193 199
194 200 Return the *TOTAL USER+SYSTEM* CPU time in seconds since the start of
195 201 the process. This is done via a call to resource.getrusage, so it
196 202 avoids the wraparound problems in time.clock()."""
197 203
198 204 u,s = resource.getrusage(resource.RUSAGE_SELF)[:2]
199 205 return u+s
200 206
201 207 def clock2():
202 208 """clock2() -> (t_user,t_system)
203 209
204 210 Similar to clock(), but return a tuple of user/system times."""
205 211 return resource.getrusage(resource.RUSAGE_SELF)[:2]
206 212
207 213 except ImportError:
208 214 # There is no distinction of user/system time under windows, so we just use
209 215 # time.clock() for everything...
210 216 clocku = clocks = clock = time.clock
211 217 def clock2():
212 218 """Under windows, system CPU time can't be measured.
213 219
214 220 This just returns clock() and zero."""
215 221 return time.clock(),0.0
216 222
217 223 def timings_out(reps,func,*args,**kw):
218 224 """timings_out(reps,func,*args,**kw) -> (t_total,t_per_call,output)
219 225
220 226 Execute a function reps times, return a tuple with the elapsed total
221 227 CPU time in seconds, the time per call and the function's output.
222 228
223 229 Under Unix, the return value is the sum of user+system time consumed by
224 230 the process, computed via the resource module. This prevents problems
225 231 related to the wraparound effect which the time.clock() function has.
226 232
227 233 Under Windows the return value is in wall clock seconds. See the
228 234 documentation for the time module for more details."""
229 235
230 236 reps = int(reps)
231 237 assert reps >=1, 'reps must be >= 1'
232 238 if reps==1:
233 239 start = clock()
234 240 out = func(*args,**kw)
235 241 tot_time = clock()-start
236 242 else:
237 243 rng = xrange(reps-1) # the last time is executed separately to store output
238 244 start = clock()
239 245 for dummy in rng: func(*args,**kw)
240 246 out = func(*args,**kw) # one last time
241 247 tot_time = clock()-start
242 248 av_time = tot_time / reps
243 249 return tot_time,av_time,out
244 250
245 251 def timings(reps,func,*args,**kw):
246 252 """timings(reps,func,*args,**kw) -> (t_total,t_per_call)
247 253
248 254 Execute a function reps times, return a tuple with the elapsed total CPU
249 255 time in seconds and the time per call. These are just the first two values
250 256 in timings_out()."""
251 257
252 258 return timings_out(reps,func,*args,**kw)[0:2]
253 259
254 260 def timing(func,*args,**kw):
255 261 """timing(func,*args,**kw) -> t_total
256 262
257 263 Execute a function once, return the elapsed total CPU time in
258 264 seconds. This is just the first value in timings_out()."""
259 265
260 266 return timings_out(1,func,*args,**kw)[0]
261 267
262 268 #****************************************************************************
263 269 # file and system
264 270
265 271 def arg_split(s,posix=False):
266 272 """Split a command line's arguments in a shell-like manner.
267 273
268 274 This is a modified version of the standard library's shlex.split()
269 275 function, but with a default of posix=False for splitting, so that quotes
270 276 in inputs are respected."""
271 277
272 278 # XXX - there may be unicode-related problems here!!! I'm not sure that
273 279 # shlex is truly unicode-safe, so it might be necessary to do
274 280 #
275 281 # s = s.encode(sys.stdin.encoding)
276 282 #
277 283 # first, to ensure that shlex gets a normal string. Input from anyone who
278 284 # knows more about unicode and shlex than I would be good to have here...
279 285 lex = shlex.shlex(s, posix=posix)
280 286 lex.whitespace_split = True
281 287 return list(lex)
282 288
283 289 def system(cmd,verbose=0,debug=0,header=''):
284 290 """Execute a system command, return its exit status.
285 291
286 292 Options:
287 293
288 294 - verbose (0): print the command to be executed.
289 295
290 296 - debug (0): only print, do not actually execute.
291 297
292 298 - header (''): Header to print on screen prior to the executed command (it
293 299 is only prepended to the command, no newlines are added).
294 300
295 301 Note: a stateful version of this function is available through the
296 302 SystemExec class."""
297 303
298 304 stat = 0
299 305 if verbose or debug: print header+cmd
300 306 sys.stdout.flush()
301 307 if not debug: stat = os.system(cmd)
302 308 return stat
303 309
304 310 def abbrev_cwd():
305 311 """ Return abbreviated version of cwd, e.g. d:mydir """
306 312 cwd = os.getcwd().replace('\\','/')
307 313 drivepart = ''
308 314 tail = cwd
309 315 if sys.platform == 'win32':
310 316 if len(cwd) < 4:
311 317 return cwd
312 318 drivepart,tail = os.path.splitdrive(cwd)
313 319
314 320
315 321 parts = tail.split('/')
316 322 if len(parts) > 2:
317 323 tail = '/'.join(parts[-2:])
318 324
319 325 return (drivepart + (
320 326 cwd == '/' and '/' or tail))
321 327
322 328
323 329 # This function is used by ipython in a lot of places to make system calls.
324 330 # We need it to be slightly different under win32, due to the vagaries of
325 331 # 'network shares'. A win32 override is below.
326 332
327 333 def shell(cmd,verbose=0,debug=0,header=''):
328 334 """Execute a command in the system shell, always return None.
329 335
330 336 Options:
331 337
332 338 - verbose (0): print the command to be executed.
333 339
334 340 - debug (0): only print, do not actually execute.
335 341
336 342 - header (''): Header to print on screen prior to the executed command (it
337 343 is only prepended to the command, no newlines are added).
338 344
339 345 Note: this is similar to genutils.system(), but it returns None so it can
340 346 be conveniently used in interactive loops without getting the return value
341 347 (typically 0) printed many times."""
342 348
343 349 stat = 0
344 350 if verbose or debug: print header+cmd
345 351 # flush stdout so we don't mangle python's buffering
346 352 sys.stdout.flush()
347 353
348 354 if not debug:
349 355 platutils.set_term_title("IPy " + cmd)
350 356 os.system(cmd)
351 357 platutils.set_term_title("IPy " + abbrev_cwd())
352 358
353 359 # override shell() for win32 to deal with network shares
354 360 if os.name in ('nt','dos'):
355 361
356 362 shell_ori = shell
357 363
358 364 def shell(cmd,verbose=0,debug=0,header=''):
359 365 if os.getcwd().startswith(r"\\"):
360 366 path = os.getcwd()
361 367 # change to c drive (cannot be on UNC-share when issuing os.system,
362 368 # as cmd.exe cannot handle UNC addresses)
363 369 os.chdir("c:")
364 370 # issue pushd to the UNC-share and then run the command
365 371 try:
366 372 shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header)
367 373 finally:
368 374 os.chdir(path)
369 375 else:
370 376 shell_ori(cmd,verbose,debug,header)
371 377
372 378 shell.__doc__ = shell_ori.__doc__
373 379
374 380 def getoutput(cmd,verbose=0,debug=0,header='',split=0):
375 381 """Dummy substitute for perl's backquotes.
376 382
377 383 Executes a command and returns the output.
378 384
379 385 Accepts the same arguments as system(), plus:
380 386
381 387 - split(0): if true, the output is returned as a list split on newlines.
382 388
383 389 Note: a stateful version of this function is available through the
384 390 SystemExec class.
385 391
386 392 This is pretty much deprecated and rarely used,
387 393 genutils.getoutputerror may be what you need.
388 394
389 395 """
390 396
391 397 if verbose or debug: print header+cmd
392 398 if not debug:
393 399 output = os.popen(cmd).read()
394 400 # stipping last \n is here for backwards compat.
395 401 if output.endswith('\n'):
396 402 output = output[:-1]
397 403 if split:
398 404 return output.split('\n')
399 405 else:
400 406 return output
401 407
402 408 def getoutputerror(cmd,verbose=0,debug=0,header='',split=0):
403 409 """Return (standard output,standard error) of executing cmd in a shell.
404 410
405 411 Accepts the same arguments as system(), plus:
406 412
407 413 - split(0): if true, each of stdout/err is returned as a list split on
408 414 newlines.
409 415
410 416 Note: a stateful version of this function is available through the
411 417 SystemExec class."""
412 418
413 419 if verbose or debug: print header+cmd
414 420 if not cmd:
415 421 if split:
416 422 return [],[]
417 423 else:
418 424 return '',''
419 425 if not debug:
420 426 pin,pout,perr = os.popen3(cmd)
421 427 tout = pout.read().rstrip()
422 428 terr = perr.read().rstrip()
423 429 pin.close()
424 430 pout.close()
425 431 perr.close()
426 432 if split:
427 433 return tout.split('\n'),terr.split('\n')
428 434 else:
429 435 return tout,terr
430 436
431 437 # for compatibility with older naming conventions
432 438 xsys = system
433 439 bq = getoutput
434 440
435 441 class SystemExec:
436 442 """Access the system and getoutput functions through a stateful interface.
437 443
438 444 Note: here we refer to the system and getoutput functions from this
439 445 library, not the ones from the standard python library.
440 446
441 447 This class offers the system and getoutput functions as methods, but the
442 448 verbose, debug and header parameters can be set for the instance (at
443 449 creation time or later) so that they don't need to be specified on each
444 450 call.
445 451
446 452 For efficiency reasons, there's no way to override the parameters on a
447 453 per-call basis other than by setting instance attributes. If you need
448 454 local overrides, it's best to directly call system() or getoutput().
449 455
450 456 The following names are provided as alternate options:
451 457 - xsys: alias to system
452 458 - bq: alias to getoutput
453 459
454 460 An instance can then be created as:
455 461 >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ')
456 462
457 463 And used as:
458 464 >>> sysexec.xsys('pwd')
459 465 >>> dirlist = sysexec.bq('ls -l')
460 466 """
461 467
462 468 def __init__(self,verbose=0,debug=0,header='',split=0):
463 469 """Specify the instance's values for verbose, debug and header."""
464 470 setattr_list(self,'verbose debug header split')
465 471
466 472 def system(self,cmd):
467 473 """Stateful interface to system(), with the same keyword parameters."""
468 474
469 475 system(cmd,self.verbose,self.debug,self.header)
470 476
471 477 def shell(self,cmd):
472 478 """Stateful interface to shell(), with the same keyword parameters."""
473 479
474 480 shell(cmd,self.verbose,self.debug,self.header)
475 481
476 482 xsys = system # alias
477 483
478 484 def getoutput(self,cmd):
479 485 """Stateful interface to getoutput()."""
480 486
481 487 return getoutput(cmd,self.verbose,self.debug,self.header,self.split)
482 488
483 489 def getoutputerror(self,cmd):
484 490 """Stateful interface to getoutputerror()."""
485 491
486 492 return getoutputerror(cmd,self.verbose,self.debug,self.header,self.split)
487 493
488 494 bq = getoutput # alias
489 495
490 496 #-----------------------------------------------------------------------------
491 497 def mutex_opts(dict,ex_op):
492 498 """Check for presence of mutually exclusive keys in a dict.
493 499
494 500 Call: mutex_opts(dict,[[op1a,op1b],[op2a,op2b]...]"""
495 501 for op1,op2 in ex_op:
496 502 if op1 in dict and op2 in dict:
497 503 raise ValueError,'\n*** ERROR in Arguments *** '\
498 504 'Options '+op1+' and '+op2+' are mutually exclusive.'
499 505
500 506 #-----------------------------------------------------------------------------
501 507 def get_py_filename(name):
502 508 """Return a valid python filename in the current directory.
503 509
504 510 If the given name is not a file, it adds '.py' and searches again.
505 511 Raises IOError with an informative message if the file isn't found."""
506 512
507 513 name = os.path.expanduser(name)
508 514 if not os.path.isfile(name) and not name.endswith('.py'):
509 515 name += '.py'
510 516 if os.path.isfile(name):
511 517 return name
512 518 else:
513 519 raise IOError,'File `%s` not found.' % name
514 520
515 521 #-----------------------------------------------------------------------------
516 522 def filefind(fname,alt_dirs = None):
517 523 """Return the given filename either in the current directory, if it
518 524 exists, or in a specified list of directories.
519 525
520 526 ~ expansion is done on all file and directory names.
521 527
522 528 Upon an unsuccessful search, raise an IOError exception."""
523 529
524 530 if alt_dirs is None:
525 531 try:
526 532 alt_dirs = get_home_dir()
527 533 except HomeDirError:
528 534 alt_dirs = os.getcwd()
529 535 search = [fname] + list_strings(alt_dirs)
530 536 search = map(os.path.expanduser,search)
531 537 #print 'search list for',fname,'list:',search # dbg
532 538 fname = search[0]
533 539 if os.path.isfile(fname):
534 540 return fname
535 541 for direc in search[1:]:
536 542 testname = os.path.join(direc,fname)
537 543 #print 'testname',testname # dbg
538 544 if os.path.isfile(testname):
539 545 return testname
540 546 raise IOError,'File' + `fname` + \
541 547 ' not found in current or supplied directories:' + `alt_dirs`
542 548
543 549 #----------------------------------------------------------------------------
544 550 def file_read(filename):
545 551 """Read a file and close it. Returns the file source."""
546 552 fobj = open(filename,'r');
547 553 source = fobj.read();
548 554 fobj.close()
549 555 return source
550 556
551 557 def file_readlines(filename):
552 558 """Read a file and close it. Returns the file source using readlines()."""
553 559 fobj = open(filename,'r');
554 560 lines = fobj.readlines();
555 561 fobj.close()
556 562 return lines
557 563
558 564 #----------------------------------------------------------------------------
559 565 def target_outdated(target,deps):
560 566 """Determine whether a target is out of date.
561 567
562 568 target_outdated(target,deps) -> 1/0
563 569
564 570 deps: list of filenames which MUST exist.
565 571 target: single filename which may or may not exist.
566 572
567 573 If target doesn't exist or is older than any file listed in deps, return
568 574 true, otherwise return false.
569 575 """
570 576 try:
571 577 target_time = os.path.getmtime(target)
572 578 except os.error:
573 579 return 1
574 580 for dep in deps:
575 581 dep_time = os.path.getmtime(dep)
576 582 if dep_time > target_time:
577 583 #print "For target",target,"Dep failed:",dep # dbg
578 584 #print "times (dep,tar):",dep_time,target_time # dbg
579 585 return 1
580 586 return 0
581 587
582 588 #-----------------------------------------------------------------------------
583 589 def target_update(target,deps,cmd):
584 590 """Update a target with a given command given a list of dependencies.
585 591
586 592 target_update(target,deps,cmd) -> runs cmd if target is outdated.
587 593
588 594 This is just a wrapper around target_outdated() which calls the given
589 595 command if target is outdated."""
590 596
591 597 if target_outdated(target,deps):
592 598 xsys(cmd)
593 599
594 600 #----------------------------------------------------------------------------
595 601 def unquote_ends(istr):
596 602 """Remove a single pair of quotes from the endpoints of a string."""
597 603
598 604 if not istr:
599 605 return istr
600 606 if (istr[0]=="'" and istr[-1]=="'") or \
601 607 (istr[0]=='"' and istr[-1]=='"'):
602 608 return istr[1:-1]
603 609 else:
604 610 return istr
605 611
606 612 #----------------------------------------------------------------------------
607 613 def process_cmdline(argv,names=[],defaults={},usage=''):
608 614 """ Process command-line options and arguments.
609 615
610 616 Arguments:
611 617
612 618 - argv: list of arguments, typically sys.argv.
613 619
614 620 - names: list of option names. See DPyGetOpt docs for details on options
615 621 syntax.
616 622
617 623 - defaults: dict of default values.
618 624
619 625 - usage: optional usage notice to print if a wrong argument is passed.
620 626
621 627 Return a dict of options and a list of free arguments."""
622 628
623 629 getopt = DPyGetOpt.DPyGetOpt()
624 630 getopt.setIgnoreCase(0)
625 631 getopt.parseConfiguration(names)
626 632
627 633 try:
628 634 getopt.processArguments(argv)
629 635 except DPyGetOpt.ArgumentError, exc:
630 636 print usage
631 637 warn('"%s"' % exc,level=4)
632 638
633 639 defaults.update(getopt.optionValues)
634 640 args = getopt.freeValues
635 641
636 642 return defaults,args
637 643
638 644 #----------------------------------------------------------------------------
639 645 def optstr2types(ostr):
640 646 """Convert a string of option names to a dict of type mappings.
641 647
642 648 optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'}
643 649
644 650 This is used to get the types of all the options in a string formatted
645 651 with the conventions of DPyGetOpt. The 'type' None is used for options
646 652 which are strings (they need no further conversion). This function's main
647 653 use is to get a typemap for use with read_dict().
648 654 """
649 655
650 656 typeconv = {None:'',int:'',float:''}
651 657 typemap = {'s':None,'i':int,'f':float}
652 658 opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)')
653 659
654 660 for w in ostr.split():
655 661 oname,alias,otype = opt_re.match(w).groups()
656 662 if otype == '' or alias == '!': # simple switches are integers too
657 663 otype = 'i'
658 664 typeconv[typemap[otype]] += oname + ' '
659 665 return typeconv
660 666
661 667 #----------------------------------------------------------------------------
662 668 def read_dict(filename,type_conv=None,**opt):
663 669
664 670 """Read a dictionary of key=value pairs from an input file, optionally
665 671 performing conversions on the resulting values.
666 672
667 673 read_dict(filename,type_conv,**opt) -> dict
668 674
669 675 Only one value per line is accepted, the format should be
670 676 # optional comments are ignored
671 677 key value\n
672 678
673 679 Args:
674 680
675 681 - type_conv: A dictionary specifying which keys need to be converted to
676 682 which types. By default all keys are read as strings. This dictionary
677 683 should have as its keys valid conversion functions for strings
678 684 (int,long,float,complex, or your own). The value for each key
679 685 (converter) should be a whitespace separated string containing the names
680 686 of all the entries in the file to be converted using that function. For
681 687 keys to be left alone, use None as the conversion function (only needed
682 688 with purge=1, see below).
683 689
684 690 - opt: dictionary with extra options as below (default in parens)
685 691
686 692 purge(0): if set to 1, all keys *not* listed in type_conv are purged out
687 693 of the dictionary to be returned. If purge is going to be used, the
688 694 set of keys to be left as strings also has to be explicitly specified
689 695 using the (non-existent) conversion function None.
690 696
691 697 fs(None): field separator. This is the key/value separator to be used
692 698 when parsing the file. The None default means any whitespace [behavior
693 699 of string.split()].
694 700
695 701 strip(0): if 1, strip string values of leading/trailinig whitespace.
696 702
697 703 warn(1): warning level if requested keys are not found in file.
698 704 - 0: silently ignore.
699 705 - 1: inform but proceed.
700 706 - 2: raise KeyError exception.
701 707
702 708 no_empty(0): if 1, remove keys with whitespace strings as a value.
703 709
704 710 unique([]): list of keys (or space separated string) which can't be
705 711 repeated. If one such key is found in the file, each new instance
706 712 overwrites the previous one. For keys not listed here, the behavior is
707 713 to make a list of all appearances.
708 714
709 715 Example:
710 716 If the input file test.ini has:
711 717 i 3
712 718 x 4.5
713 719 y 5.5
714 720 s hi ho
715 721 Then:
716 722
717 723 >>> type_conv={int:'i',float:'x',None:'s'}
718 724 >>> read_dict('test.ini')
719 725 {'i': '3', 's': 'hi ho', 'x': '4.5', 'y': '5.5'}
720 726 >>> read_dict('test.ini',type_conv)
721 727 {'i': 3, 's': 'hi ho', 'x': 4.5, 'y': '5.5'}
722 728 >>> read_dict('test.ini',type_conv,purge=1)
723 729 {'i': 3, 's': 'hi ho', 'x': 4.5}
724 730 """
725 731
726 732 # starting config
727 733 opt.setdefault('purge',0)
728 734 opt.setdefault('fs',None) # field sep defaults to any whitespace
729 735 opt.setdefault('strip',0)
730 736 opt.setdefault('warn',1)
731 737 opt.setdefault('no_empty',0)
732 738 opt.setdefault('unique','')
733 739 if type(opt['unique']) in StringTypes:
734 740 unique_keys = qw(opt['unique'])
735 741 elif type(opt['unique']) in (types.TupleType,types.ListType):
736 742 unique_keys = opt['unique']
737 743 else:
738 744 raise ValueError, 'Unique keys must be given as a string, List or Tuple'
739 745
740 746 dict = {}
741 747 # first read in table of values as strings
742 748 file = open(filename,'r')
743 749 for line in file.readlines():
744 750 line = line.strip()
745 751 if len(line) and line[0]=='#': continue
746 752 if len(line)>0:
747 753 lsplit = line.split(opt['fs'],1)
748 754 try:
749 755 key,val = lsplit
750 756 except ValueError:
751 757 key,val = lsplit[0],''
752 758 key = key.strip()
753 759 if opt['strip']: val = val.strip()
754 760 if val == "''" or val == '""': val = ''
755 761 if opt['no_empty'] and (val=='' or val.isspace()):
756 762 continue
757 763 # if a key is found more than once in the file, build a list
758 764 # unless it's in the 'unique' list. In that case, last found in file
759 765 # takes precedence. User beware.
760 766 try:
761 767 if dict[key] and key in unique_keys:
762 768 dict[key] = val
763 769 elif type(dict[key]) is types.ListType:
764 770 dict[key].append(val)
765 771 else:
766 772 dict[key] = [dict[key],val]
767 773 except KeyError:
768 774 dict[key] = val
769 775 # purge if requested
770 776 if opt['purge']:
771 777 accepted_keys = qwflat(type_conv.values())
772 778 for key in dict.keys():
773 779 if key in accepted_keys: continue
774 780 del(dict[key])
775 781 # now convert if requested
776 782 if type_conv==None: return dict
777 783 conversions = type_conv.keys()
778 784 try: conversions.remove(None)
779 785 except: pass
780 786 for convert in conversions:
781 787 for val in qw(type_conv[convert]):
782 788 try:
783 789 dict[val] = convert(dict[val])
784 790 except KeyError,e:
785 791 if opt['warn'] == 0:
786 792 pass
787 793 elif opt['warn'] == 1:
788 794 print >>sys.stderr, 'Warning: key',val,\
789 795 'not found in file',filename
790 796 elif opt['warn'] == 2:
791 797 raise KeyError,e
792 798 else:
793 799 raise ValueError,'Warning level must be 0,1 or 2'
794 800
795 801 return dict
796 802
797 803 #----------------------------------------------------------------------------
798 804 def flag_calls(func):
799 805 """Wrap a function to detect and flag when it gets called.
800 806
801 807 This is a decorator which takes a function and wraps it in a function with
802 808 a 'called' attribute. wrapper.called is initialized to False.
803 809
804 810 The wrapper.called attribute is set to False right before each call to the
805 811 wrapped function, so if the call fails it remains False. After the call
806 812 completes, wrapper.called is set to True and the output is returned.
807 813
808 814 Testing for truth in wrapper.called allows you to determine if a call to
809 815 func() was attempted and succeeded."""
810 816
811 817 def wrapper(*args,**kw):
812 818 wrapper.called = False
813 819 out = func(*args,**kw)
814 820 wrapper.called = True
815 821 return out
816 822
817 823 wrapper.called = False
818 824 wrapper.__doc__ = func.__doc__
819 825 return wrapper
820 826
821 827 #----------------------------------------------------------------------------
822 828 def dhook_wrap(func,*a,**k):
823 829 """Wrap a function call in a sys.displayhook controller.
824 830
825 831 Returns a wrapper around func which calls func, with all its arguments and
826 832 keywords unmodified, using the default sys.displayhook. Since IPython
827 833 modifies sys.displayhook, it breaks the behavior of certain systems that
828 834 rely on the default behavior, notably doctest.
829 835 """
830 836
831 837 def f(*a,**k):
832 838
833 839 dhook_s = sys.displayhook
834 840 sys.displayhook = sys.__displayhook__
835 841 try:
836 842 out = func(*a,**k)
837 843 finally:
838 844 sys.displayhook = dhook_s
839 845
840 846 return out
841 847
842 848 f.__doc__ = func.__doc__
843 849 return f
844 850
845 851 #----------------------------------------------------------------------------
846 852 def doctest_reload():
847 853 """Properly reload doctest to reuse it interactively.
848 854
849 855 This routine:
850 856
851 857 - reloads doctest
852 858
853 859 - resets its global 'master' attribute to None, so that multiple uses of
854 860 the module interactively don't produce cumulative reports.
855 861
856 862 - Monkeypatches its core test runner method to protect it from IPython's
857 863 modified displayhook. Doctest expects the default displayhook behavior
858 864 deep down, so our modification breaks it completely. For this reason, a
859 865 hard monkeypatch seems like a reasonable solution rather than asking
860 866 users to manually use a different doctest runner when under IPython."""
861 867
862 868 import doctest
863 869 reload(doctest)
864 870 doctest.master=None
865 871
866 872 try:
867 873 doctest.DocTestRunner
868 874 except AttributeError:
869 875 # This is only for python 2.3 compatibility, remove once we move to
870 876 # 2.4 only.
871 877 pass
872 878 else:
873 879 doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run)
874 880
875 881 #----------------------------------------------------------------------------
876 882 class HomeDirError(Error):
877 883 pass
878 884
879 885 def get_home_dir():
880 886 """Return the closest possible equivalent to a 'home' directory.
881 887
882 888 We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH.
883 889
884 890 Currently only Posix and NT are implemented, a HomeDirError exception is
885 891 raised for all other OSes. """
886 892
887 893 isdir = os.path.isdir
888 894 env = os.environ
889 895
890 896 # first, check py2exe distribution root directory for _ipython.
891 897 # This overrides all. Normally does not exist.
892 898
893 899 if '\\library.zip\\' in IPython.__file__.lower():
894 900 root, rest = IPython.__file__.lower().split('library.zip')
895 901 if isdir(root + '_ipython'):
896 902 os.environ["IPYKITROOT"] = root.rstrip('\\')
897 903 return root
898 904
899 905 try:
900 906 homedir = env['HOME']
901 907 if not isdir(homedir):
902 908 # in case a user stuck some string which does NOT resolve to a
903 909 # valid path, it's as good as if we hadn't foud it
904 910 raise KeyError
905 911 return homedir
906 912 except KeyError:
907 913 if os.name == 'posix':
908 914 raise HomeDirError,'undefined $HOME, IPython can not proceed.'
909 915 elif os.name == 'nt':
910 916 # For some strange reason, win9x returns 'nt' for os.name.
911 917 try:
912 918 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
913 919 if not isdir(homedir):
914 920 homedir = os.path.join(env['USERPROFILE'])
915 921 if not isdir(homedir):
916 922 raise HomeDirError
917 923 return homedir
918 924 except:
919 925 try:
920 926 # Use the registry to get the 'My Documents' folder.
921 927 import _winreg as wreg
922 928 key = wreg.OpenKey(wreg.HKEY_CURRENT_USER,
923 929 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
924 930 homedir = wreg.QueryValueEx(key,'Personal')[0]
925 931 key.Close()
926 932 if not isdir(homedir):
927 933 e = ('Invalid "Personal" folder registry key '
928 934 'typically "My Documents".\n'
929 935 'Value: %s\n'
930 936 'This is not a valid directory on your system.' %
931 937 homedir)
932 938 raise HomeDirError(e)
933 939 return homedir
934 940 except HomeDirError:
935 941 raise
936 942 except:
937 943 return 'C:\\'
938 944 elif os.name == 'dos':
939 945 # Desperate, may do absurd things in classic MacOS. May work under DOS.
940 946 return 'C:\\'
941 947 else:
942 948 raise HomeDirError,'support for your operating system not implemented.'
943 949
944 950 #****************************************************************************
945 951 # strings and text
946 952
947 953 class LSString(str):
948 954 """String derivative with a special access attributes.
949 955
950 956 These are normal strings, but with the special attributes:
951 957
952 958 .l (or .list) : value as list (split on newlines).
953 959 .n (or .nlstr): original value (the string itself).
954 960 .s (or .spstr): value as whitespace-separated string.
955 961 .p (or .paths): list of path objects
956 962
957 963 Any values which require transformations are computed only once and
958 964 cached.
959 965
960 966 Such strings are very useful to efficiently interact with the shell, which
961 967 typically only understands whitespace-separated options for commands."""
962 968
963 969 def get_list(self):
964 970 try:
965 971 return self.__list
966 972 except AttributeError:
967 973 self.__list = self.split('\n')
968 974 return self.__list
969 975
970 976 l = list = property(get_list)
971 977
972 978 def get_spstr(self):
973 979 try:
974 980 return self.__spstr
975 981 except AttributeError:
976 982 self.__spstr = self.replace('\n',' ')
977 983 return self.__spstr
978 984
979 985 s = spstr = property(get_spstr)
980 986
981 987 def get_nlstr(self):
982 988 return self
983 989
984 990 n = nlstr = property(get_nlstr)
985 991
986 992 def get_paths(self):
987 993 try:
988 994 return self.__paths
989 995 except AttributeError:
990 996 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
991 997 return self.__paths
992 998
993 999 p = paths = property(get_paths)
994 1000
995 1001 def print_lsstring(arg):
996 1002 """ Prettier (non-repr-like) and more informative printer for LSString """
997 1003 print "LSString (.p, .n, .l, .s available). Value:"
998 1004 print arg
999 1005
1000 1006 print_lsstring = result_display.when_type(LSString)(print_lsstring)
1001 1007
1002 1008 #----------------------------------------------------------------------------
1003 1009 class SList(list):
1004 1010 """List derivative with a special access attributes.
1005 1011
1006 1012 These are normal lists, but with the special attributes:
1007 1013
1008 1014 .l (or .list) : value as list (the list itself).
1009 1015 .n (or .nlstr): value as a string, joined on newlines.
1010 1016 .s (or .spstr): value as a string, joined on spaces.
1011 1017 .p (or .paths): list of path objects
1012 1018
1013 1019 Any values which require transformations are computed only once and
1014 1020 cached."""
1015 1021
1016 1022 def get_list(self):
1017 1023 return self
1018 1024
1019 1025 l = list = property(get_list)
1020 1026
1021 1027 def get_spstr(self):
1022 1028 try:
1023 1029 return self.__spstr
1024 1030 except AttributeError:
1025 1031 self.__spstr = ' '.join(self)
1026 1032 return self.__spstr
1027 1033
1028 1034 s = spstr = property(get_spstr)
1029 1035
1030 1036 def get_nlstr(self):
1031 1037 try:
1032 1038 return self.__nlstr
1033 1039 except AttributeError:
1034 1040 self.__nlstr = '\n'.join(self)
1035 1041 return self.__nlstr
1036 1042
1037 1043 n = nlstr = property(get_nlstr)
1038 1044
1039 1045 def get_paths(self):
1040 1046 try:
1041 1047 return self.__paths
1042 1048 except AttributeError:
1043 1049 self.__paths = [path(p) for p in self if os.path.exists(p)]
1044 1050 return self.__paths
1045 1051
1046 1052 p = paths = property(get_paths)
1047 1053
1048 1054 def grep(self, pattern, prune = False):
1049 1055 """ Return all strings matching 'pattern' (a regex or callable)
1050 1056
1051 1057 This is case-insensitive. If prune is true, return all items
1052 1058 NOT matching the pattern.
1053 1059
1054 1060 Examples::
1055 1061
1056 1062 a.grep( lambda x: x.startswith('C') )
1057 1063 a.grep('Cha.*log', prune=1)
1058 1064 """
1059 1065 if isinstance(pattern, basestring):
1060 1066 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
1061 1067 else:
1062 1068 pred = pattern
1063 1069 if not prune:
1064 1070 return SList([el for el in self if pred(el)])
1065 1071 else:
1066 1072 return SList([el for el in self if not pred(el)])
1067 1073 def fields(self, *fields):
1068 1074 """ Collect whitespace-separated fields from string list
1069 1075
1070 1076 Allows quick awk-like usage of string lists.
1071 1077
1072 1078 Example data (in var a, created by 'a = !ls -l')::
1073 1079 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
1074 1080 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
1075 1081
1076 1082 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
1077 1083 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
1078 1084 (note the joining by space).
1079 1085
1080 1086 IndexErrors are ignored.
1081 1087
1082 1088 Without args, fields() just split()'s the strings.
1083 1089 """
1084 1090 if len(fields) == 0:
1085 1091 return [el.split() for el in self]
1086 1092
1087 1093 res = SList()
1088 1094 for el in [f.split() for f in self]:
1089 1095 lineparts = []
1090 1096
1091 1097 for fd in fields:
1092 1098 try:
1093 1099 lineparts.append(el[fd])
1094 1100 except IndexError:
1095 1101 pass
1096 1102 if lineparts:
1097 1103 res.append(" ".join(lineparts))
1098 1104
1099 1105 return res
1100 1106
1101 1107
1102 1108
1103 1109
1104 1110
1105 1111 def print_slist(arg):
1106 1112 """ Prettier (non-repr-like) and more informative printer for SList """
1107 1113 print "SList (.p, .n, .l, .s, .grep(), .fields() available). Value:"
1108 1114 nlprint(arg)
1109 1115
1110 1116 print_slist = result_display.when_type(SList)(print_slist)
1111 1117
1112 1118
1113 1119
1114 1120 #----------------------------------------------------------------------------
1115 1121 def esc_quotes(strng):
1116 1122 """Return the input string with single and double quotes escaped out"""
1117 1123
1118 1124 return strng.replace('"','\\"').replace("'","\\'")
1119 1125
1120 1126 #----------------------------------------------------------------------------
1121 1127 def make_quoted_expr(s):
1122 1128 """Return string s in appropriate quotes, using raw string if possible.
1123 1129
1124 1130 Effectively this turns string: cd \ao\ao\
1125 1131 to: r"cd \ao\ao\_"[:-1]
1126 1132
1127 1133 Note the use of raw string and padding at the end to allow trailing backslash.
1128 1134
1129 1135 """
1130 1136
1131 1137 tail = ''
1132 1138 tailpadding = ''
1133 1139 raw = ''
1134 1140 if "\\" in s:
1135 1141 raw = 'r'
1136 1142 if s.endswith('\\'):
1137 1143 tail = '[:-1]'
1138 1144 tailpadding = '_'
1139 1145 if '"' not in s:
1140 1146 quote = '"'
1141 1147 elif "'" not in s:
1142 1148 quote = "'"
1143 1149 elif '"""' not in s and not s.endswith('"'):
1144 1150 quote = '"""'
1145 1151 elif "'''" not in s and not s.endswith("'"):
1146 1152 quote = "'''"
1147 1153 else:
1148 1154 # give up, backslash-escaped string will do
1149 1155 return '"%s"' % esc_quotes(s)
1150 1156 res = raw + quote + s + tailpadding + quote + tail
1151 1157 return res
1152 1158
1153 1159
1154 1160 #----------------------------------------------------------------------------
1155 1161 def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'):
1156 1162 """Take multiple lines of input.
1157 1163
1158 1164 A list with each line of input as a separate element is returned when a
1159 1165 termination string is entered (defaults to a single '.'). Input can also
1160 1166 terminate via EOF (^D in Unix, ^Z-RET in Windows).
1161 1167
1162 1168 Lines of input which end in \\ are joined into single entries (and a
1163 1169 secondary continuation prompt is issued as long as the user terminates
1164 1170 lines with \\). This allows entering very long strings which are still
1165 1171 meant to be treated as single entities.
1166 1172 """
1167 1173
1168 1174 try:
1169 1175 if header:
1170 1176 header += '\n'
1171 1177 lines = [raw_input(header + ps1)]
1172 1178 except EOFError:
1173 1179 return []
1174 1180 terminate = [terminate_str]
1175 1181 try:
1176 1182 while lines[-1:] != terminate:
1177 1183 new_line = raw_input(ps1)
1178 1184 while new_line.endswith('\\'):
1179 1185 new_line = new_line[:-1] + raw_input(ps2)
1180 1186 lines.append(new_line)
1181 1187
1182 1188 return lines[:-1] # don't return the termination command
1183 1189 except EOFError:
1184 1190 print
1185 1191 return lines
1186 1192
1187 1193 #----------------------------------------------------------------------------
1188 1194 def raw_input_ext(prompt='', ps2='... '):
1189 1195 """Similar to raw_input(), but accepts extended lines if input ends with \\."""
1190 1196
1191 1197 line = raw_input(prompt)
1192 1198 while line.endswith('\\'):
1193 1199 line = line[:-1] + raw_input(ps2)
1194 1200 return line
1195 1201
1196 1202 #----------------------------------------------------------------------------
1197 1203 def ask_yes_no(prompt,default=None):
1198 1204 """Asks a question and returns a boolean (y/n) answer.
1199 1205
1200 1206 If default is given (one of 'y','n'), it is used if the user input is
1201 1207 empty. Otherwise the question is repeated until an answer is given.
1202 1208
1203 1209 An EOF is treated as the default answer. If there is no default, an
1204 1210 exception is raised to prevent infinite loops.
1205 1211
1206 1212 Valid answers are: y/yes/n/no (match is not case sensitive)."""
1207 1213
1208 1214 answers = {'y':True,'n':False,'yes':True,'no':False}
1209 1215 ans = None
1210 1216 while ans not in answers.keys():
1211 1217 try:
1212 1218 ans = raw_input(prompt+' ').lower()
1213 1219 if not ans: # response was an empty string
1214 1220 ans = default
1215 1221 except KeyboardInterrupt:
1216 1222 pass
1217 1223 except EOFError:
1218 1224 if default in answers.keys():
1219 1225 ans = default
1220 1226 print
1221 1227 else:
1222 1228 raise
1223 1229
1224 1230 return answers[ans]
1225 1231
1226 1232 #----------------------------------------------------------------------------
1227 1233 def marquee(txt='',width=78,mark='*'):
1228 1234 """Return the input string centered in a 'marquee'."""
1229 1235 if not txt:
1230 1236 return (mark*width)[:width]
1231 1237 nmark = (width-len(txt)-2)/len(mark)/2
1232 1238 if nmark < 0: nmark =0
1233 1239 marks = mark*nmark
1234 1240 return '%s %s %s' % (marks,txt,marks)
1235 1241
1236 1242 #----------------------------------------------------------------------------
1237 1243 class EvalDict:
1238 1244 """
1239 1245 Emulate a dict which evaluates its contents in the caller's frame.
1240 1246
1241 1247 Usage:
1242 1248 >>>number = 19
1243 1249 >>>text = "python"
1244 1250 >>>print "%(text.capitalize())s %(number/9.0).1f rules!" % EvalDict()
1245 1251 """
1246 1252
1247 1253 # This version is due to sismex01@hebmex.com on c.l.py, and is basically a
1248 1254 # modified (shorter) version of:
1249 1255 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66018 by
1250 1256 # Skip Montanaro (skip@pobox.com).
1251 1257
1252 1258 def __getitem__(self, name):
1253 1259 frame = sys._getframe(1)
1254 1260 return eval(name, frame.f_globals, frame.f_locals)
1255 1261
1256 1262 EvalString = EvalDict # for backwards compatibility
1257 1263 #----------------------------------------------------------------------------
1258 1264 def qw(words,flat=0,sep=None,maxsplit=-1):
1259 1265 """Similar to Perl's qw() operator, but with some more options.
1260 1266
1261 1267 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
1262 1268
1263 1269 words can also be a list itself, and with flat=1, the output will be
1264 1270 recursively flattened. Examples:
1265 1271
1266 1272 >>> qw('1 2')
1267 1273 ['1', '2']
1268 1274 >>> qw(['a b','1 2',['m n','p q']])
1269 1275 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
1270 1276 >>> qw(['a b','1 2',['m n','p q']],flat=1)
1271 1277 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q'] """
1272 1278
1273 1279 if type(words) in StringTypes:
1274 1280 return [word.strip() for word in words.split(sep,maxsplit)
1275 1281 if word and not word.isspace() ]
1276 1282 if flat:
1277 1283 return flatten(map(qw,words,[1]*len(words)))
1278 1284 return map(qw,words)
1279 1285
1280 1286 #----------------------------------------------------------------------------
1281 1287 def qwflat(words,sep=None,maxsplit=-1):
1282 1288 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
1283 1289 return qw(words,1,sep,maxsplit)
1284 1290
1285 1291 #----------------------------------------------------------------------------
1286 1292 def qw_lol(indata):
1287 1293 """qw_lol('a b') -> [['a','b']],
1288 1294 otherwise it's just a call to qw().
1289 1295
1290 1296 We need this to make sure the modules_some keys *always* end up as a
1291 1297 list of lists."""
1292 1298
1293 1299 if type(indata) in StringTypes:
1294 1300 return [qw(indata)]
1295 1301 else:
1296 1302 return qw(indata)
1297 1303
1298 1304 #-----------------------------------------------------------------------------
1299 1305 def list_strings(arg):
1300 1306 """Always return a list of strings, given a string or list of strings
1301 1307 as input."""
1302 1308
1303 1309 if type(arg) in StringTypes: return [arg]
1304 1310 else: return arg
1305 1311
1306 1312 #----------------------------------------------------------------------------
1307 1313 def grep(pat,list,case=1):
1308 1314 """Simple minded grep-like function.
1309 1315 grep(pat,list) returns occurrences of pat in list, None on failure.
1310 1316
1311 1317 It only does simple string matching, with no support for regexps. Use the
1312 1318 option case=0 for case-insensitive matching."""
1313 1319
1314 1320 # This is pretty crude. At least it should implement copying only references
1315 1321 # to the original data in case it's big. Now it copies the data for output.
1316 1322 out=[]
1317 1323 if case:
1318 1324 for term in list:
1319 1325 if term.find(pat)>-1: out.append(term)
1320 1326 else:
1321 1327 lpat=pat.lower()
1322 1328 for term in list:
1323 1329 if term.lower().find(lpat)>-1: out.append(term)
1324 1330
1325 1331 if len(out): return out
1326 1332 else: return None
1327 1333
1328 1334 #----------------------------------------------------------------------------
1329 1335 def dgrep(pat,*opts):
1330 1336 """Return grep() on dir()+dir(__builtins__).
1331 1337
1332 1338 A very common use of grep() when working interactively."""
1333 1339
1334 1340 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
1335 1341
1336 1342 #----------------------------------------------------------------------------
1337 1343 def idgrep(pat):
1338 1344 """Case-insensitive dgrep()"""
1339 1345
1340 1346 return dgrep(pat,0)
1341 1347
1342 1348 #----------------------------------------------------------------------------
1343 1349 def igrep(pat,list):
1344 1350 """Synonym for case-insensitive grep."""
1345 1351
1346 1352 return grep(pat,list,case=0)
1347 1353
1348 1354 #----------------------------------------------------------------------------
1349 1355 def indent(str,nspaces=4,ntabs=0):
1350 1356 """Indent a string a given number of spaces or tabstops.
1351 1357
1352 1358 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
1353 1359 """
1354 1360 if str is None:
1355 1361 return
1356 1362 ind = '\t'*ntabs+' '*nspaces
1357 1363 outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind))
1358 1364 if outstr.endswith(os.linesep+ind):
1359 1365 return outstr[:-len(ind)]
1360 1366 else:
1361 1367 return outstr
1362 1368
1363 1369 #-----------------------------------------------------------------------------
1364 1370 def native_line_ends(filename,backup=1):
1365 1371 """Convert (in-place) a file to line-ends native to the current OS.
1366 1372
1367 1373 If the optional backup argument is given as false, no backup of the
1368 1374 original file is left. """
1369 1375
1370 1376 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
1371 1377
1372 1378 bak_filename = filename + backup_suffixes[os.name]
1373 1379
1374 1380 original = open(filename).read()
1375 1381 shutil.copy2(filename,bak_filename)
1376 1382 try:
1377 1383 new = open(filename,'wb')
1378 1384 new.write(os.linesep.join(original.splitlines()))
1379 1385 new.write(os.linesep) # ALWAYS put an eol at the end of the file
1380 1386 new.close()
1381 1387 except:
1382 1388 os.rename(bak_filename,filename)
1383 1389 if not backup:
1384 1390 try:
1385 1391 os.remove(bak_filename)
1386 1392 except:
1387 1393 pass
1388 1394
1389 1395 #----------------------------------------------------------------------------
1390 1396 def get_pager_cmd(pager_cmd = None):
1391 1397 """Return a pager command.
1392 1398
1393 1399 Makes some attempts at finding an OS-correct one."""
1394 1400
1395 1401 if os.name == 'posix':
1396 1402 default_pager_cmd = 'less -r' # -r for color control sequences
1397 1403 elif os.name in ['nt','dos']:
1398 1404 default_pager_cmd = 'type'
1399 1405
1400 1406 if pager_cmd is None:
1401 1407 try:
1402 1408 pager_cmd = os.environ['PAGER']
1403 1409 except:
1404 1410 pager_cmd = default_pager_cmd
1405 1411 return pager_cmd
1406 1412
1407 1413 #-----------------------------------------------------------------------------
1408 1414 def get_pager_start(pager,start):
1409 1415 """Return the string for paging files with an offset.
1410 1416
1411 1417 This is the '+N' argument which less and more (under Unix) accept.
1412 1418 """
1413 1419
1414 1420 if pager in ['less','more']:
1415 1421 if start:
1416 1422 start_string = '+' + str(start)
1417 1423 else:
1418 1424 start_string = ''
1419 1425 else:
1420 1426 start_string = ''
1421 1427 return start_string
1422 1428
1423 1429 #----------------------------------------------------------------------------
1424 1430 # (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch()
1425 1431 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
1426 1432 import msvcrt
1427 1433 def page_more():
1428 1434 """ Smart pausing between pages
1429 1435
1430 1436 @return: True if need print more lines, False if quit
1431 1437 """
1432 1438 Term.cout.write('---Return to continue, q to quit--- ')
1433 1439 ans = msvcrt.getch()
1434 1440 if ans in ("q", "Q"):
1435 1441 result = False
1436 1442 else:
1437 1443 result = True
1438 1444 Term.cout.write("\b"*37 + " "*37 + "\b"*37)
1439 1445 return result
1440 1446 else:
1441 1447 def page_more():
1442 1448 ans = raw_input('---Return to continue, q to quit--- ')
1443 1449 if ans.lower().startswith('q'):
1444 1450 return False
1445 1451 else:
1446 1452 return True
1447 1453
1448 1454 esc_re = re.compile(r"(\x1b[^m]+m)")
1449 1455
1450 1456 def page_dumb(strng,start=0,screen_lines=25):
1451 1457 """Very dumb 'pager' in Python, for when nothing else works.
1452 1458
1453 1459 Only moves forward, same interface as page(), except for pager_cmd and
1454 1460 mode."""
1455 1461
1456 1462 out_ln = strng.splitlines()[start:]
1457 1463 screens = chop(out_ln,screen_lines-1)
1458 1464 if len(screens) == 1:
1459 1465 print >>Term.cout, os.linesep.join(screens[0])
1460 1466 else:
1461 1467 last_escape = ""
1462 1468 for scr in screens[0:-1]:
1463 1469 hunk = os.linesep.join(scr)
1464 1470 print >>Term.cout, last_escape + hunk
1465 1471 if not page_more():
1466 1472 return
1467 1473 esc_list = esc_re.findall(hunk)
1468 1474 if len(esc_list) > 0:
1469 1475 last_escape = esc_list[-1]
1470 1476 print >>Term.cout, last_escape + os.linesep.join(screens[-1])
1471 1477
1472 1478 #----------------------------------------------------------------------------
1473 1479 def page(strng,start=0,screen_lines=0,pager_cmd = None):
1474 1480 """Print a string, piping through a pager after a certain length.
1475 1481
1476 1482 The screen_lines parameter specifies the number of *usable* lines of your
1477 1483 terminal screen (total lines minus lines you need to reserve to show other
1478 1484 information).
1479 1485
1480 1486 If you set screen_lines to a number <=0, page() will try to auto-determine
1481 1487 your screen size and will only use up to (screen_size+screen_lines) for
1482 1488 printing, paging after that. That is, if you want auto-detection but need
1483 1489 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
1484 1490 auto-detection without any lines reserved simply use screen_lines = 0.
1485 1491
1486 1492 If a string won't fit in the allowed lines, it is sent through the
1487 1493 specified pager command. If none given, look for PAGER in the environment,
1488 1494 and ultimately default to less.
1489 1495
1490 1496 If no system pager works, the string is sent through a 'dumb pager'
1491 1497 written in python, very simplistic.
1492 1498 """
1493 1499
1494 1500 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
1495 1501 TERM = os.environ.get('TERM','dumb')
1496 1502 if TERM in ['dumb','emacs'] and os.name != 'nt':
1497 1503 print strng
1498 1504 return
1499 1505 # chop off the topmost part of the string we don't want to see
1500 1506 str_lines = strng.split(os.linesep)[start:]
1501 1507 str_toprint = os.linesep.join(str_lines)
1502 1508 num_newlines = len(str_lines)
1503 1509 len_str = len(str_toprint)
1504 1510
1505 1511 # Dumb heuristics to guesstimate number of on-screen lines the string
1506 1512 # takes. Very basic, but good enough for docstrings in reasonable
1507 1513 # terminals. If someone later feels like refining it, it's not hard.
1508 1514 numlines = max(num_newlines,int(len_str/80)+1)
1509 1515
1510 1516 if os.name == "nt":
1511 1517 screen_lines_def = get_console_size(defaulty=25)[1]
1512 1518 else:
1513 1519 screen_lines_def = 25 # default value if we can't auto-determine
1514 1520
1515 1521 # auto-determine screen size
1516 1522 if screen_lines <= 0:
1517 1523 if TERM=='xterm':
1518 1524 try:
1519 1525 import curses
1520 1526 if hasattr(curses,'initscr'):
1521 1527 use_curses = 1
1522 1528 else:
1523 1529 use_curses = 0
1524 1530 except ImportError:
1525 1531 use_curses = 0
1526 1532 else:
1527 1533 # curses causes problems on many terminals other than xterm.
1528 1534 use_curses = 0
1529 1535 if use_curses:
1530 1536 scr = curses.initscr()
1531 1537 screen_lines_real,screen_cols = scr.getmaxyx()
1532 1538 curses.endwin()
1533 1539 screen_lines += screen_lines_real
1534 1540 #print '***Screen size:',screen_lines_real,'lines x',\
1535 1541 #screen_cols,'columns.' # dbg
1536 1542 else:
1537 1543 screen_lines += screen_lines_def
1538 1544
1539 1545 #print 'numlines',numlines,'screenlines',screen_lines # dbg
1540 1546 if numlines <= screen_lines :
1541 1547 #print '*** normal print' # dbg
1542 1548 print >>Term.cout, str_toprint
1543 1549 else:
1544 1550 # Try to open pager and default to internal one if that fails.
1545 1551 # All failure modes are tagged as 'retval=1', to match the return
1546 1552 # value of a failed system command. If any intermediate attempt
1547 1553 # sets retval to 1, at the end we resort to our own page_dumb() pager.
1548 1554 pager_cmd = get_pager_cmd(pager_cmd)
1549 1555 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1550 1556 if os.name == 'nt':
1551 1557 if pager_cmd.startswith('type'):
1552 1558 # The default WinXP 'type' command is failing on complex strings.
1553 1559 retval = 1
1554 1560 else:
1555 1561 tmpname = tempfile.mktemp('.txt')
1556 1562 tmpfile = file(tmpname,'wt')
1557 1563 tmpfile.write(strng)
1558 1564 tmpfile.close()
1559 1565 cmd = "%s < %s" % (pager_cmd,tmpname)
1560 1566 if os.system(cmd):
1561 1567 retval = 1
1562 1568 else:
1563 1569 retval = None
1564 1570 os.remove(tmpname)
1565 1571 else:
1566 1572 try:
1567 1573 retval = None
1568 1574 # if I use popen4, things hang. No idea why.
1569 1575 #pager,shell_out = os.popen4(pager_cmd)
1570 1576 pager = os.popen(pager_cmd,'w')
1571 1577 pager.write(strng)
1572 1578 pager.close()
1573 1579 retval = pager.close() # success returns None
1574 1580 except IOError,msg: # broken pipe when user quits
1575 1581 if msg.args == (32,'Broken pipe'):
1576 1582 retval = None
1577 1583 else:
1578 1584 retval = 1
1579 1585 except OSError:
1580 1586 # Other strange problems, sometimes seen in Win2k/cygwin
1581 1587 retval = 1
1582 1588 if retval is not None:
1583 1589 page_dumb(strng,screen_lines=screen_lines)
1584 1590
1585 1591 #----------------------------------------------------------------------------
1586 1592 def page_file(fname,start = 0, pager_cmd = None):
1587 1593 """Page a file, using an optional pager command and starting line.
1588 1594 """
1589 1595
1590 1596 pager_cmd = get_pager_cmd(pager_cmd)
1591 1597 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1592 1598
1593 1599 try:
1594 1600 if os.environ['TERM'] in ['emacs','dumb']:
1595 1601 raise EnvironmentError
1596 1602 xsys(pager_cmd + ' ' + fname)
1597 1603 except:
1598 1604 try:
1599 1605 if start > 0:
1600 1606 start -= 1
1601 1607 page(open(fname).read(),start)
1602 1608 except:
1603 1609 print 'Unable to show file',`fname`
1604 1610
1605 1611 #----------------------------------------------------------------------------
1606 1612 def snip_print(str,width = 75,print_full = 0,header = ''):
1607 1613 """Print a string snipping the midsection to fit in width.
1608 1614
1609 1615 print_full: mode control:
1610 1616 - 0: only snip long strings
1611 1617 - 1: send to page() directly.
1612 1618 - 2: snip long strings and ask for full length viewing with page()
1613 1619 Return 1 if snipping was necessary, 0 otherwise."""
1614 1620
1615 1621 if print_full == 1:
1616 1622 page(header+str)
1617 1623 return 0
1618 1624
1619 1625 print header,
1620 1626 if len(str) < width:
1621 1627 print str
1622 1628 snip = 0
1623 1629 else:
1624 1630 whalf = int((width -5)/2)
1625 1631 print str[:whalf] + ' <...> ' + str[-whalf:]
1626 1632 snip = 1
1627 1633 if snip and print_full == 2:
1628 1634 if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
1629 1635 page(str)
1630 1636 return snip
1631 1637
1632 1638 #****************************************************************************
1633 1639 # lists, dicts and structures
1634 1640
1635 1641 def belong(candidates,checklist):
1636 1642 """Check whether a list of items appear in a given list of options.
1637 1643
1638 1644 Returns a list of 1 and 0, one for each candidate given."""
1639 1645
1640 1646 return [x in checklist for x in candidates]
1641 1647
1642 1648 #----------------------------------------------------------------------------
1643 1649 def uniq_stable(elems):
1644 1650 """uniq_stable(elems) -> list
1645 1651
1646 1652 Return from an iterable, a list of all the unique elements in the input,
1647 1653 but maintaining the order in which they first appear.
1648 1654
1649 1655 A naive solution to this problem which just makes a dictionary with the
1650 1656 elements as keys fails to respect the stability condition, since
1651 1657 dictionaries are unsorted by nature.
1652 1658
1653 1659 Note: All elements in the input must be valid dictionary keys for this
1654 1660 routine to work, as it internally uses a dictionary for efficiency
1655 1661 reasons."""
1656 1662
1657 1663 unique = []
1658 1664 unique_dict = {}
1659 1665 for nn in elems:
1660 1666 if nn not in unique_dict:
1661 1667 unique.append(nn)
1662 1668 unique_dict[nn] = None
1663 1669 return unique
1664 1670
1665 1671 #----------------------------------------------------------------------------
1666 1672 class NLprinter:
1667 1673 """Print an arbitrarily nested list, indicating index numbers.
1668 1674
1669 1675 An instance of this class called nlprint is available and callable as a
1670 1676 function.
1671 1677
1672 1678 nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent'
1673 1679 and using 'sep' to separate the index from the value. """
1674 1680
1675 1681 def __init__(self):
1676 1682 self.depth = 0
1677 1683
1678 1684 def __call__(self,lst,pos='',**kw):
1679 1685 """Prints the nested list numbering levels."""
1680 1686 kw.setdefault('indent',' ')
1681 1687 kw.setdefault('sep',': ')
1682 1688 kw.setdefault('start',0)
1683 1689 kw.setdefault('stop',len(lst))
1684 1690 # we need to remove start and stop from kw so they don't propagate
1685 1691 # into a recursive call for a nested list.
1686 1692 start = kw['start']; del kw['start']
1687 1693 stop = kw['stop']; del kw['stop']
1688 1694 if self.depth == 0 and 'header' in kw.keys():
1689 1695 print kw['header']
1690 1696
1691 1697 for idx in range(start,stop):
1692 1698 elem = lst[idx]
1693 1699 if type(elem)==type([]):
1694 1700 self.depth += 1
1695 1701 self.__call__(elem,itpl('$pos$idx,'),**kw)
1696 1702 self.depth -= 1
1697 1703 else:
1698 1704 printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem')
1699 1705
1700 1706 nlprint = NLprinter()
1701 1707 #----------------------------------------------------------------------------
1702 1708 def all_belong(candidates,checklist):
1703 1709 """Check whether a list of items ALL appear in a given list of options.
1704 1710
1705 1711 Returns a single 1 or 0 value."""
1706 1712
1707 1713 return 1-(0 in [x in checklist for x in candidates])
1708 1714
1709 1715 #----------------------------------------------------------------------------
1710 1716 def sort_compare(lst1,lst2,inplace = 1):
1711 1717 """Sort and compare two lists.
1712 1718
1713 1719 By default it does it in place, thus modifying the lists. Use inplace = 0
1714 1720 to avoid that (at the cost of temporary copy creation)."""
1715 1721 if not inplace:
1716 1722 lst1 = lst1[:]
1717 1723 lst2 = lst2[:]
1718 1724 lst1.sort(); lst2.sort()
1719 1725 return lst1 == lst2
1720 1726
1721 1727 #----------------------------------------------------------------------------
1722 1728 def mkdict(**kwargs):
1723 1729 """Return a dict from a keyword list.
1724 1730
1725 1731 It's just syntactic sugar for making ditcionary creation more convenient:
1726 1732 # the standard way
1727 1733 >>>data = { 'red' : 1, 'green' : 2, 'blue' : 3 }
1728 1734 # a cleaner way
1729 1735 >>>data = dict(red=1, green=2, blue=3)
1730 1736
1731 1737 If you need more than this, look at the Struct() class."""
1732 1738
1733 1739 return kwargs
1734 1740
1735 1741 #----------------------------------------------------------------------------
1736 1742 def list2dict(lst):
1737 1743 """Takes a list of (key,value) pairs and turns it into a dict."""
1738 1744
1739 1745 dic = {}
1740 1746 for k,v in lst: dic[k] = v
1741 1747 return dic
1742 1748
1743 1749 #----------------------------------------------------------------------------
1744 1750 def list2dict2(lst,default=''):
1745 1751 """Takes a list and turns it into a dict.
1746 1752 Much slower than list2dict, but more versatile. This version can take
1747 1753 lists with sublists of arbitrary length (including sclars)."""
1748 1754
1749 1755 dic = {}
1750 1756 for elem in lst:
1751 1757 if type(elem) in (types.ListType,types.TupleType):
1752 1758 size = len(elem)
1753 1759 if size == 0:
1754 1760 pass
1755 1761 elif size == 1:
1756 1762 dic[elem] = default
1757 1763 else:
1758 1764 k,v = elem[0], elem[1:]
1759 1765 if len(v) == 1: v = v[0]
1760 1766 dic[k] = v
1761 1767 else:
1762 1768 dic[elem] = default
1763 1769 return dic
1764 1770
1765 1771 #----------------------------------------------------------------------------
1766 1772 def flatten(seq):
1767 1773 """Flatten a list of lists (NOT recursive, only works for 2d lists)."""
1768 1774
1769 1775 return [x for subseq in seq for x in subseq]
1770 1776
1771 1777 #----------------------------------------------------------------------------
1772 1778 def get_slice(seq,start=0,stop=None,step=1):
1773 1779 """Get a slice of a sequence with variable step. Specify start,stop,step."""
1774 1780 if stop == None:
1775 1781 stop = len(seq)
1776 1782 item = lambda i: seq[i]
1777 1783 return map(item,xrange(start,stop,step))
1778 1784
1779 1785 #----------------------------------------------------------------------------
1780 1786 def chop(seq,size):
1781 1787 """Chop a sequence into chunks of the given size."""
1782 1788 chunk = lambda i: seq[i:i+size]
1783 1789 return map(chunk,xrange(0,len(seq),size))
1784 1790
1785 1791 #----------------------------------------------------------------------------
1786 1792 # with is a keyword as of python 2.5, so this function is renamed to withobj
1787 1793 # from its old 'with' name.
1788 1794 def with_obj(object, **args):
1789 1795 """Set multiple attributes for an object, similar to Pascal's with.
1790 1796
1791 1797 Example:
1792 1798 with_obj(jim,
1793 1799 born = 1960,
1794 1800 haircolour = 'Brown',
1795 1801 eyecolour = 'Green')
1796 1802
1797 1803 Credit: Greg Ewing, in
1798 1804 http://mail.python.org/pipermail/python-list/2001-May/040703.html.
1799 1805
1800 1806 NOTE: up until IPython 0.7.2, this was called simply 'with', but 'with'
1801 1807 has become a keyword for Python 2.5, so we had to rename it."""
1802 1808
1803 1809 object.__dict__.update(args)
1804 1810
1805 1811 #----------------------------------------------------------------------------
1806 1812 def setattr_list(obj,alist,nspace = None):
1807 1813 """Set a list of attributes for an object taken from a namespace.
1808 1814
1809 1815 setattr_list(obj,alist,nspace) -> sets in obj all the attributes listed in
1810 1816 alist with their values taken from nspace, which must be a dict (something
1811 1817 like locals() will often do) If nspace isn't given, locals() of the
1812 1818 *caller* is used, so in most cases you can omit it.
1813 1819
1814 1820 Note that alist can be given as a string, which will be automatically
1815 1821 split into a list on whitespace. If given as a list, it must be a list of
1816 1822 *strings* (the variable names themselves), not of variables."""
1817 1823
1818 1824 # this grabs the local variables from the *previous* call frame -- that is
1819 1825 # the locals from the function that called setattr_list().
1820 1826 # - snipped from weave.inline()
1821 1827 if nspace is None:
1822 1828 call_frame = sys._getframe().f_back
1823 1829 nspace = call_frame.f_locals
1824 1830
1825 1831 if type(alist) in StringTypes:
1826 1832 alist = alist.split()
1827 1833 for attr in alist:
1828 1834 val = eval(attr,nspace)
1829 1835 setattr(obj,attr,val)
1830 1836
1831 1837 #----------------------------------------------------------------------------
1832 1838 def getattr_list(obj,alist,*args):
1833 1839 """getattr_list(obj,alist[, default]) -> attribute list.
1834 1840
1835 1841 Get a list of named attributes for an object. When a default argument is
1836 1842 given, it is returned when the attribute doesn't exist; without it, an
1837 1843 exception is raised in that case.
1838 1844
1839 1845 Note that alist can be given as a string, which will be automatically
1840 1846 split into a list on whitespace. If given as a list, it must be a list of
1841 1847 *strings* (the variable names themselves), not of variables."""
1842 1848
1843 1849 if type(alist) in StringTypes:
1844 1850 alist = alist.split()
1845 1851 if args:
1846 1852 if len(args)==1:
1847 1853 default = args[0]
1848 1854 return map(lambda attr: getattr(obj,attr,default),alist)
1849 1855 else:
1850 1856 raise ValueError,'getattr_list() takes only one optional argument'
1851 1857 else:
1852 1858 return map(lambda attr: getattr(obj,attr),alist)
1853 1859
1854 1860 #----------------------------------------------------------------------------
1855 1861 def map_method(method,object_list,*argseq,**kw):
1856 1862 """map_method(method,object_list,*args,**kw) -> list
1857 1863
1858 1864 Return a list of the results of applying the methods to the items of the
1859 1865 argument sequence(s). If more than one sequence is given, the method is
1860 1866 called with an argument list consisting of the corresponding item of each
1861 1867 sequence. All sequences must be of the same length.
1862 1868
1863 1869 Keyword arguments are passed verbatim to all objects called.
1864 1870
1865 1871 This is Python code, so it's not nearly as fast as the builtin map()."""
1866 1872
1867 1873 out_list = []
1868 1874 idx = 0
1869 1875 for object in object_list:
1870 1876 try:
1871 1877 handler = getattr(object, method)
1872 1878 except AttributeError:
1873 1879 out_list.append(None)
1874 1880 else:
1875 1881 if argseq:
1876 1882 args = map(lambda lst:lst[idx],argseq)
1877 1883 #print 'ob',object,'hand',handler,'ar',args # dbg
1878 1884 out_list.append(handler(args,**kw))
1879 1885 else:
1880 1886 out_list.append(handler(**kw))
1881 1887 idx += 1
1882 1888 return out_list
1883 1889
1884 1890 #----------------------------------------------------------------------------
1885 1891 def get_class_members(cls):
1886 1892 ret = dir(cls)
1887 1893 if hasattr(cls,'__bases__'):
1888 1894 for base in cls.__bases__:
1889 1895 ret.extend(get_class_members(base))
1890 1896 return ret
1891 1897
1892 1898 #----------------------------------------------------------------------------
1893 1899 def dir2(obj):
1894 1900 """dir2(obj) -> list of strings
1895 1901
1896 1902 Extended version of the Python builtin dir(), which does a few extra
1897 1903 checks, and supports common objects with unusual internals that confuse
1898 1904 dir(), such as Traits and PyCrust.
1899 1905
1900 1906 This version is guaranteed to return only a list of true strings, whereas
1901 1907 dir() returns anything that objects inject into themselves, even if they
1902 1908 are later not really valid for attribute access (many extension libraries
1903 1909 have such bugs).
1904 1910 """
1905 1911
1906 1912 # Start building the attribute list via dir(), and then complete it
1907 1913 # with a few extra special-purpose calls.
1908 1914 words = dir(obj)
1909 1915
1910 1916 if hasattr(obj,'__class__'):
1911 1917 words.append('__class__')
1912 1918 words.extend(get_class_members(obj.__class__))
1913 1919 #if '__base__' in words: 1/0
1914 1920
1915 1921 # Some libraries (such as traits) may introduce duplicates, we want to
1916 1922 # track and clean this up if it happens
1917 1923 may_have_dupes = False
1918 1924
1919 1925 # this is the 'dir' function for objects with Enthought's traits
1920 1926 if hasattr(obj, 'trait_names'):
1921 1927 try:
1922 1928 words.extend(obj.trait_names())
1923 1929 may_have_dupes = True
1924 1930 except TypeError:
1925 1931 # This will happen if `obj` is a class and not an instance.
1926 1932 pass
1927 1933
1928 1934 # Support for PyCrust-style _getAttributeNames magic method.
1929 1935 if hasattr(obj, '_getAttributeNames'):
1930 1936 try:
1931 1937 words.extend(obj._getAttributeNames())
1932 1938 may_have_dupes = True
1933 1939 except TypeError:
1934 1940 # `obj` is a class and not an instance. Ignore
1935 1941 # this error.
1936 1942 pass
1937 1943
1938 1944 if may_have_dupes:
1939 1945 # eliminate possible duplicates, as some traits may also
1940 1946 # appear as normal attributes in the dir() call.
1941 1947 words = list(set(words))
1942 1948 words.sort()
1943 1949
1944 1950 # filter out non-string attributes which may be stuffed by dir() calls
1945 1951 # and poor coding in third-party modules
1946 1952 return [w for w in words if isinstance(w, basestring)]
1947 1953
1948 1954 #----------------------------------------------------------------------------
1949 1955 def import_fail_info(mod_name,fns=None):
1950 1956 """Inform load failure for a module."""
1951 1957
1952 1958 if fns == None:
1953 1959 warn("Loading of %s failed.\n" % (mod_name,))
1954 1960 else:
1955 1961 warn("Loading of %s from %s failed.\n" % (fns,mod_name))
1956 1962
1957 1963 #----------------------------------------------------------------------------
1958 1964 # Proposed popitem() extension, written as a method
1959 1965
1960 1966
1961 1967 class NotGiven: pass
1962 1968
1963 1969 def popkey(dct,key,default=NotGiven):
1964 1970 """Return dct[key] and delete dct[key].
1965 1971
1966 1972 If default is given, return it if dct[key] doesn't exist, otherwise raise
1967 1973 KeyError. """
1968 1974
1969 1975 try:
1970 1976 val = dct[key]
1971 1977 except KeyError:
1972 1978 if default is NotGiven:
1973 1979 raise
1974 1980 else:
1975 1981 return default
1976 1982 else:
1977 1983 del dct[key]
1978 1984 return val
1979 1985
1980 1986 def wrap_deprecated(func, suggest = '<nothing>'):
1981 1987 def newFunc(*args, **kwargs):
1982 1988 warnings.warn("Call to deprecated function %s, use %s instead" %
1983 1989 ( func.__name__, suggest),
1984 1990 category=DeprecationWarning,
1985 1991 stacklevel = 2)
1986 1992 return func(*args, **kwargs)
1987 1993 return newFunc
1988 1994
1989 1995 #*************************** end of file <genutils.py> **********************
1990 1996
General Comments 0
You need to be logged in to leave comments. Login now