##// END OF EJS Templates
Rename ipipe._default to ipipe.noitem (which makes...
walter.doerwald -
Show More
@@ -1,1413 +1,1413 b''
1 1 # -*- coding: iso-8859-1 -*-
2 2
3 3 import curses
4 4
5 5 import astyle, ipipe
6 6
7 7
8 8 _ibrowse_help = """
9 9 down
10 10 Move the cursor to the next line.
11 11
12 12 up
13 13 Move the cursor to the previous line.
14 14
15 15 pagedown
16 16 Move the cursor down one page (minus overlap).
17 17
18 18 pageup
19 19 Move the cursor up one page (minus overlap).
20 20
21 21 left
22 22 Move the cursor left.
23 23
24 24 right
25 25 Move the cursor right.
26 26
27 27 home
28 28 Move the cursor to the first column.
29 29
30 30 end
31 31 Move the cursor to the last column.
32 32
33 33 prevattr
34 34 Move the cursor one attribute column to the left.
35 35
36 36 nextattr
37 37 Move the cursor one attribute column to the right.
38 38
39 39 pick
40 40 'Pick' the object under the cursor (i.e. the row the cursor is on). This
41 41 leaves the browser and returns the picked object to the caller. (In IPython
42 42 this object will be available as the '_' variable.)
43 43
44 44 pickattr
45 45 'Pick' the attribute under the cursor (i.e. the row/column the cursor is on).
46 46
47 47 pickallattrs
48 48 Pick' the complete column under the cursor (i.e. the attribute under the
49 49 cursor) from all currently fetched objects. These attributes will be returned
50 50 as a list.
51 51
52 52 tooglemark
53 53 Mark/unmark the object under the cursor. Marked objects have a '!' after the
54 54 row number).
55 55
56 56 pickmarked
57 57 'Pick' marked objects. Marked objects will be returned as a list.
58 58
59 59 pickmarkedattr
60 60 'Pick' the attribute under the cursor from all marked objects (This returns a
61 61 list).
62 62
63 63 enterdefault
64 64 Enter the object under the cursor. (what this mean depends on the object
65 65 itself (i.e. how it implements the '__xiter__' method). This opens a new
66 66 browser 'level'.
67 67
68 68 enter
69 69 Enter the object under the cursor. If the object provides different enter
70 70 modes a menu of all modes will be presented; choose one and enter it (via the
71 71 'enter' or 'enterdefault' command).
72 72
73 73 enterattr
74 74 Enter the attribute under the cursor.
75 75
76 76 leave
77 77 Leave the current browser level and go back to the previous one.
78 78
79 79 detail
80 80 Show a detail view of the object under the cursor. This shows the name, type,
81 81 doc string and value of the object attributes (and it might show more
82 82 attributes than in the list view, depending on the object).
83 83
84 84 detailattr
85 85 Show a detail view of the attribute under the cursor.
86 86
87 87 markrange
88 88 Mark all objects from the last marked object before the current cursor
89 89 position to the cursor position.
90 90
91 91 sortattrasc
92 92 Sort the objects (in ascending order) using the attribute under the cursor as
93 93 the sort key.
94 94
95 95 sortattrdesc
96 96 Sort the objects (in descending order) using the attribute under the cursor as
97 97 the sort key.
98 98
99 99 goto
100 100 Jump to a row. The row number can be entered at the bottom of the screen.
101 101
102 102 find
103 103 Search forward for a row. At the bottom of the screen the condition can be
104 104 entered.
105 105
106 106 findbackwards
107 107 Search backward for a row. At the bottom of the screen the condition can be
108 108 entered.
109 109
110 110 help
111 111 This screen.
112 112 """
113 113
114 114
115 115 class UnassignedKeyError(Exception):
116 116 """
117 117 Exception that is used for reporting unassigned keys.
118 118 """
119 119
120 120
121 121 class UnknownCommandError(Exception):
122 122 """
123 123 Exception that is used for reporting unknown command (this should never
124 124 happen).
125 125 """
126 126
127 127
128 128 class CommandError(Exception):
129 129 """
130 130 Exception that is used for reporting that a command can't be executed.
131 131 """
132 132
133 133
134 134 class _BrowserCachedItem(object):
135 135 # This is used internally by ``ibrowse`` to store a item together with its
136 136 # marked status.
137 137 __slots__ = ("item", "marked")
138 138
139 139 def __init__(self, item):
140 140 self.item = item
141 141 self.marked = False
142 142
143 143
144 144 class _BrowserHelp(object):
145 145 style_header = astyle.Style.fromstr("red:blacK")
146 146 # This is used internally by ``ibrowse`` for displaying the help screen.
147 147 def __init__(self, browser):
148 148 self.browser = browser
149 149
150 150 def __xrepr__(self, mode):
151 151 yield (-1, True)
152 152 if mode == "header" or mode == "footer":
153 153 yield (astyle.style_default, "ibrowse help screen")
154 154 else:
155 155 yield (astyle.style_default, repr(self))
156 156
157 157 def __xiter__(self, mode):
158 158 # Get reverse key mapping
159 159 allkeys = {}
160 160 for (key, cmd) in self.browser.keymap.iteritems():
161 161 allkeys.setdefault(cmd, []).append(key)
162 162
163 163 fields = ("key", "description")
164 164
165 165 for (i, command) in enumerate(_ibrowse_help.strip().split("\n\n")):
166 166 if i:
167 167 yield Fields(fields, key="", description="")
168 168
169 169 (name, description) = command.split("\n", 1)
170 170 keys = allkeys.get(name, [])
171 171 lines = textwrap.wrap(description, 60)
172 172
173 173 yield Fields(fields, description=astyle.Text((self.style_header, name)))
174 174 for i in xrange(max(len(keys), len(lines))):
175 175 try:
176 176 key = self.browser.keylabel(keys[i])
177 177 except IndexError:
178 178 key = ""
179 179 try:
180 180 line = lines[i]
181 181 except IndexError:
182 182 line = ""
183 183 yield Fields(fields, key=key, description=line)
184 184
185 185
186 186 class _BrowserLevel(object):
187 187 # This is used internally to store the state (iterator, fetch items,
188 188 # position of cursor and screen, etc.) of one browser level
189 189 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
190 190 # a stack.
191 191 def __init__(self, browser, input, iterator, mainsizey, *attrs):
192 192 self.browser = browser
193 193 self.input = input
194 194 self.header = [x for x in ipipe.xrepr(input, "header") if not isinstance(x[0], int)]
195 195 # iterator for the input
196 196 self.iterator = iterator
197 197
198 198 # is the iterator exhausted?
199 199 self.exhausted = False
200 200
201 201 # attributes to be display (autodetected if empty)
202 202 self.attrs = attrs
203 203
204 204 # fetched items (+ marked flag)
205 205 self.items = ipipe.deque()
206 206
207 207 # Number of marked objects
208 208 self.marked = 0
209 209
210 210 # Vertical cursor position
211 211 self.cury = 0
212 212
213 213 # Horizontal cursor position
214 214 self.curx = 0
215 215
216 216 # Index of first data column
217 217 self.datastartx = 0
218 218
219 219 # Index of first data line
220 220 self.datastarty = 0
221 221
222 222 # height of the data display area
223 223 self.mainsizey = mainsizey
224 224
225 225 # width of the data display area (changes when scrolling)
226 226 self.mainsizex = 0
227 227
228 228 # Size of row number (changes when scrolling)
229 229 self.numbersizex = 0
230 230
231 231 # Attribute names to display (in this order)
232 232 self.displayattrs = []
233 233
234 234 # index and name of attribute under the cursor
235 self.displayattr = (None, ipipe._default)
235 self.displayattr = (None, ipipe.noitem)
236 236
237 237 # Maps attribute names to column widths
238 238 self.colwidths = {}
239 239
240 240 self.fetch(mainsizey)
241 241 self.calcdisplayattrs()
242 242 # formatted attributes for the items on screen
243 243 # (i.e. self.items[self.datastarty:self.datastarty+self.mainsizey])
244 244 self.displayrows = [self.getrow(i) for i in xrange(len(self.items))]
245 245 self.calcwidths()
246 246 self.calcdisplayattr()
247 247
248 248 def fetch(self, count):
249 249 # Try to fill ``self.items`` with at least ``count`` objects.
250 250 have = len(self.items)
251 251 while not self.exhausted and have < count:
252 252 try:
253 253 item = self.iterator.next()
254 254 except StopIteration:
255 255 self.exhausted = True
256 256 break
257 257 else:
258 258 have += 1
259 259 self.items.append(_BrowserCachedItem(item))
260 260
261 261 def calcdisplayattrs(self):
262 262 # Calculate which attributes are available from the objects that are
263 263 # currently visible on screen (and store it in ``self.displayattrs``)
264 264 attrnames = set()
265 265 # If the browser object specifies a fixed list of attributes,
266 266 # simply use it.
267 267 if self.attrs:
268 268 self.displayattrs = self.attrs
269 269 else:
270 270 self.displayattrs = []
271 271 endy = min(self.datastarty+self.mainsizey, len(self.items))
272 272 for i in xrange(self.datastarty, endy):
273 273 for attrname in ipipe.xattrs(self.items[i].item, "default"):
274 274 if attrname not in attrnames:
275 275 self.displayattrs.append(attrname)
276 276 attrnames.add(attrname)
277 277
278 278 def getrow(self, i):
279 279 # Return a dictinary with the attributes for the object
280 280 # ``self.items[i]``. Attribute names are taken from
281 281 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
282 282 # called before.
283 283 row = {}
284 284 item = self.items[i].item
285 285 for attrname in self.displayattrs:
286 286 try:
287 value = ipipe._getattr(item, attrname, ipipe._default)
287 value = ipipe._getattr(item, attrname, ipipe.noitem)
288 288 except (KeyboardInterrupt, SystemExit):
289 289 raise
290 290 except Exception, exc:
291 291 value = exc
292 292 # only store attribute if it exists (or we got an exception)
293 if value is not ipipe._default:
293 if value is not ipipe.noitem:
294 294 parts = []
295 295 totallength = 0
296 296 align = None
297 297 full = False
298 298 # Collect parts until we have enough
299 299 for part in ipipe.xrepr(value, "cell"):
300 300 # part gives (alignment, stop)
301 301 # instead of (style, text)
302 302 if isinstance(part[0], int):
303 303 # only consider the first occurence
304 304 if align is None:
305 305 align = part[0]
306 306 full = part[1]
307 307 else:
308 308 parts.append(part)
309 309 totallength += len(part[1])
310 310 if totallength >= self.browser.maxattrlength and not full:
311 311 parts.append((astyle.style_ellisis, "..."))
312 312 totallength += 3
313 313 break
314 314 # remember alignment, length and colored parts
315 315 row[attrname] = (align, totallength, parts)
316 316 return row
317 317
318 318 def calcwidths(self):
319 319 # Recalculate the displayed fields and their width.
320 320 # ``calcdisplayattrs()'' must have been called and the cache
321 321 # for attributes of the objects on screen (``self.displayrows``)
322 322 # must have been filled. This returns a dictionary mapping
323 323 # colmn names to width.
324 324 self.colwidths = {}
325 325 for row in self.displayrows:
326 326 for attrname in self.displayattrs:
327 327 try:
328 328 length = row[attrname][1]
329 329 except KeyError:
330 330 length = 0
331 331 # always add attribute to colwidths, even if it doesn't exist
332 332 if attrname not in self.colwidths:
333 333 self.colwidths[attrname] = len(ipipe._attrname(attrname))
334 334 newwidth = max(self.colwidths[attrname], length)
335 335 self.colwidths[attrname] = newwidth
336 336
337 337 # How many characters do we need to paint the item number?
338 338 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
339 339 # How must space have we got to display data?
340 340 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
341 341 # width of all columns
342 342 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
343 343
344 344 def calcdisplayattr(self):
345 345 # Find out on which attribute the cursor is on and store this
346 346 # information in ``self.displayattr``.
347 347 pos = 0
348 348 for (i, attrname) in enumerate(self.displayattrs):
349 349 if pos+self.colwidths[attrname] >= self.curx:
350 350 self.displayattr = (i, attrname)
351 351 break
352 352 pos += self.colwidths[attrname]+1
353 353 else:
354 self.displayattr = (None, ipipe._default)
354 self.displayattr = (None, ipipe.noitem)
355 355
356 356 def moveto(self, x, y, refresh=False):
357 357 # Move the cursor to the position ``(x,y)`` (in data coordinates,
358 358 # not in screen coordinates). If ``refresh`` is true, all cached
359 359 # values will be recalculated (e.g. because the list has been
360 360 # resorted, so screen positions etc. are no longer valid).
361 361 olddatastarty = self.datastarty
362 362 oldx = self.curx
363 363 oldy = self.cury
364 364 x = int(x+0.5)
365 365 y = int(y+0.5)
366 366 newx = x # remember where we wanted to move
367 367 newy = y # remember where we wanted to move
368 368
369 369 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
370 370 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
371 371
372 372 # Make sure that the cursor didn't leave the main area vertically
373 373 if y < 0:
374 374 y = 0
375 375 self.fetch(y+scrollbordery+1) # try to get more items
376 376 if y >= len(self.items):
377 377 y = max(0, len(self.items)-1)
378 378
379 379 # Make sure that the cursor stays on screen vertically
380 380 if y < self.datastarty+scrollbordery:
381 381 self.datastarty = max(0, y-scrollbordery)
382 382 elif y >= self.datastarty+self.mainsizey-scrollbordery:
383 383 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
384 384 len(self.items)-self.mainsizey))
385 385
386 386 if refresh: # Do we need to refresh the complete display?
387 387 self.calcdisplayattrs()
388 388 endy = min(self.datastarty+self.mainsizey, len(self.items))
389 389 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
390 390 self.calcwidths()
391 391 # Did we scroll vertically => update displayrows
392 392 # and various other attributes
393 393 elif self.datastarty != olddatastarty:
394 394 # Recalculate which attributes we have to display
395 395 olddisplayattrs = self.displayattrs
396 396 self.calcdisplayattrs()
397 397 # If there are new attributes, recreate the cache
398 398 if self.displayattrs != olddisplayattrs:
399 399 endy = min(self.datastarty+self.mainsizey, len(self.items))
400 400 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
401 401 elif self.datastarty<olddatastarty: # we did scroll up
402 402 # drop rows from the end
403 403 del self.displayrows[self.datastarty-olddatastarty:]
404 404 # fetch new items
405 405 for i in xrange(olddatastarty-1,
406 406 self.datastarty-1, -1):
407 407 try:
408 408 row = self.getrow(i)
409 409 except IndexError:
410 410 # we didn't have enough objects to fill the screen
411 411 break
412 412 self.displayrows.insert(0, row)
413 413 else: # we did scroll down
414 414 # drop rows from the start
415 415 del self.displayrows[:self.datastarty-olddatastarty]
416 416 # fetch new items
417 417 for i in xrange(olddatastarty+self.mainsizey,
418 418 self.datastarty+self.mainsizey):
419 419 try:
420 420 row = self.getrow(i)
421 421 except IndexError:
422 422 # we didn't have enough objects to fill the screen
423 423 break
424 424 self.displayrows.append(row)
425 425 self.calcwidths()
426 426
427 427 # Make sure that the cursor didn't leave the data area horizontally
428 428 if x < 0:
429 429 x = 0
430 430 elif x >= self.datasizex:
431 431 x = max(0, self.datasizex-1)
432 432
433 433 # Make sure that the cursor stays on screen horizontally
434 434 if x < self.datastartx+scrollborderx:
435 435 self.datastartx = max(0, x-scrollborderx)
436 436 elif x >= self.datastartx+self.mainsizex-scrollborderx:
437 437 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
438 438 self.datasizex-self.mainsizex))
439 439
440 440 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
441 441 self.browser.beep()
442 442 else:
443 443 self.curx = x
444 444 self.cury = y
445 445 self.calcdisplayattr()
446 446
447 447 def sort(self, key, reverse=False):
448 448 """
449 449 Sort the currently list of items using the key function ``key``. If
450 450 ``reverse`` is true the sort order is reversed.
451 451 """
452 452 curitem = self.items[self.cury] # Remember where the cursor is now
453 453
454 454 # Sort items
455 455 def realkey(item):
456 456 return key(item.item)
457 457 self.items = ipipe.deque(sorted(self.items, key=realkey, reverse=reverse))
458 458
459 459 # Find out where the object under the cursor went
460 460 cury = self.cury
461 461 for (i, item) in enumerate(self.items):
462 462 if item is curitem:
463 463 cury = i
464 464 break
465 465
466 466 self.moveto(self.curx, cury, refresh=True)
467 467
468 468
469 469 class ibrowse(ipipe.Display):
470 470 # Show this many lines from the previous screen when paging horizontally
471 471 pageoverlapx = 1
472 472
473 473 # Show this many lines from the previous screen when paging vertically
474 474 pageoverlapy = 1
475 475
476 476 # Start scrolling when the cursor is less than this number of columns
477 477 # away from the left or right screen edge
478 478 scrollborderx = 10
479 479
480 480 # Start scrolling when the cursor is less than this number of lines
481 481 # away from the top or bottom screen edge
482 482 scrollbordery = 5
483 483
484 484 # Accelerate by this factor when scrolling horizontally
485 485 acceleratex = 1.05
486 486
487 487 # Accelerate by this factor when scrolling vertically
488 488 acceleratey = 1.05
489 489
490 490 # The maximum horizontal scroll speed
491 491 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
492 492 maxspeedx = 0.5
493 493
494 494 # The maximum vertical scroll speed
495 495 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
496 496 maxspeedy = 0.5
497 497
498 498 # The maximum number of header lines for browser level
499 499 # if the nesting is deeper, only the innermost levels are displayed
500 500 maxheaders = 5
501 501
502 502 # The approximate maximum length of a column entry
503 503 maxattrlength = 200
504 504
505 505 # Styles for various parts of the GUI
506 506 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
507 507 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
508 508 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
509 509 style_colheader = astyle.Style.fromstr("blue:white:reverse")
510 510 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
511 511 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
512 512 style_number = astyle.Style.fromstr("blue:white:reverse")
513 513 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
514 514 style_sep = astyle.Style.fromstr("blue:black")
515 515 style_data = astyle.Style.fromstr("white:black")
516 516 style_datapad = astyle.Style.fromstr("blue:black:bold")
517 517 style_footer = astyle.Style.fromstr("black:white")
518 518 style_report = astyle.Style.fromstr("white:black")
519 519
520 520 # Column separator in header
521 521 headersepchar = "|"
522 522
523 523 # Character for padding data cell entries
524 524 datapadchar = "."
525 525
526 526 # Column separator in data area
527 527 datasepchar = "|"
528 528
529 529 # Character to use for "empty" cell (i.e. for non-existing attributes)
530 530 nodatachar = "-"
531 531
532 532 # Prompts for modes that require keyboard input
533 533 prompts = {
534 534 "goto": "goto object #: ",
535 535 "find": "find expression: ",
536 536 "findbackwards": "find backwards expression: "
537 537 }
538 538
539 539 # Maps curses key codes to "function" names
540 540 keymap = {
541 541 ord("q"): "quit",
542 542 curses.KEY_UP: "up",
543 543 curses.KEY_DOWN: "down",
544 544 curses.KEY_PPAGE: "pageup",
545 545 curses.KEY_NPAGE: "pagedown",
546 546 curses.KEY_LEFT: "left",
547 547 curses.KEY_RIGHT: "right",
548 548 curses.KEY_HOME: "home",
549 549 curses.KEY_END: "end",
550 550 ord("<"): "prevattr",
551 551 0x1b: "prevattr", # SHIFT-TAB
552 552 ord(">"): "nextattr",
553 553 ord("\t"):"nextattr", # TAB
554 554 ord("p"): "pick",
555 555 ord("P"): "pickattr",
556 556 ord("C"): "pickallattrs",
557 557 ord("m"): "pickmarked",
558 558 ord("M"): "pickmarkedattr",
559 559 ord("\n"): "enterdefault",
560 560 # FIXME: What's happening here?
561 561 8: "leave",
562 562 127: "leave",
563 563 curses.KEY_BACKSPACE: "leave",
564 564 ord("x"): "leave",
565 565 ord("h"): "help",
566 566 ord("e"): "enter",
567 567 ord("E"): "enterattr",
568 568 ord("d"): "detail",
569 569 ord("D"): "detailattr",
570 570 ord(" "): "tooglemark",
571 571 ord("r"): "markrange",
572 572 ord("v"): "sortattrasc",
573 573 ord("V"): "sortattrdesc",
574 574 ord("g"): "goto",
575 575 ord("f"): "find",
576 576 ord("b"): "findbackwards",
577 577 }
578 578
579 579 def __init__(self, *attrs):
580 580 """
581 581 Create a new browser. If ``attrs`` is not empty, it is the list
582 582 of attributes that will be displayed in the browser, otherwise
583 583 these will be determined by the objects on screen.
584 584 """
585 585 self.attrs = attrs
586 586
587 587 # Stack of browser levels
588 588 self.levels = []
589 589 # how many colums to scroll (Changes when accelerating)
590 590 self.stepx = 1.
591 591
592 592 # how many rows to scroll (Changes when accelerating)
593 593 self.stepy = 1.
594 594
595 595 # Beep on the edges of the data area? (Will be set to ``False``
596 596 # once the cursor hits the edge of the screen, so we don't get
597 597 # multiple beeps).
598 598 self._dobeep = True
599 599
600 600 # Cache for registered ``curses`` colors and styles.
601 601 self._styles = {}
602 602 self._colors = {}
603 603 self._maxcolor = 1
604 604
605 605 # How many header lines do we want to paint (the numbers of levels
606 606 # we have, but with an upper bound)
607 607 self._headerlines = 1
608 608
609 609 # Index of first header line
610 610 self._firstheaderline = 0
611 611
612 612 # curses window
613 613 self.scr = None
614 614 # report in the footer line (error, executed command etc.)
615 615 self._report = None
616 616
617 617 # value to be returned to the caller (set by commands)
618 618 self.returnvalue = None
619 619
620 620 # The mode the browser is in
621 621 # e.g. normal browsing or entering an argument for a command
622 622 self.mode = "default"
623 623
624 624 # The partially entered row number for the goto command
625 625 self.goto = ""
626 626
627 627 def nextstepx(self, step):
628 628 """
629 629 Accelerate horizontally.
630 630 """
631 631 return max(1., min(step*self.acceleratex,
632 632 self.maxspeedx*self.levels[-1].mainsizex))
633 633
634 634 def nextstepy(self, step):
635 635 """
636 636 Accelerate vertically.
637 637 """
638 638 return max(1., min(step*self.acceleratey,
639 639 self.maxspeedy*self.levels[-1].mainsizey))
640 640
641 641 def getstyle(self, style):
642 642 """
643 643 Register the ``style`` with ``curses`` or get it from the cache,
644 644 if it has been registered before.
645 645 """
646 646 try:
647 647 return self._styles[style.fg, style.bg, style.attrs]
648 648 except KeyError:
649 649 attrs = 0
650 650 for b in astyle.A2CURSES:
651 651 if style.attrs & b:
652 652 attrs |= astyle.A2CURSES[b]
653 653 try:
654 654 color = self._colors[style.fg, style.bg]
655 655 except KeyError:
656 656 curses.init_pair(
657 657 self._maxcolor,
658 658 astyle.COLOR2CURSES[style.fg],
659 659 astyle.COLOR2CURSES[style.bg]
660 660 )
661 661 color = curses.color_pair(self._maxcolor)
662 662 self._colors[style.fg, style.bg] = color
663 663 self._maxcolor += 1
664 664 c = color | attrs
665 665 self._styles[style.fg, style.bg, style.attrs] = c
666 666 return c
667 667
668 668 def addstr(self, y, x, begx, endx, text, style):
669 669 """
670 670 A version of ``curses.addstr()`` that can handle ``x`` coordinates
671 671 that are outside the screen.
672 672 """
673 673 text2 = text[max(0, begx-x):max(0, endx-x)]
674 674 if text2:
675 675 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
676 676 return len(text)
677 677
678 678 def addchr(self, y, x, begx, endx, c, l, style):
679 679 x0 = max(x, begx)
680 680 x1 = min(x+l, endx)
681 681 if x1>x0:
682 682 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
683 683 return l
684 684
685 685 def _calcheaderlines(self, levels):
686 686 # Calculate how many headerlines do we have to display, if we have
687 687 # ``levels`` browser levels
688 688 if levels is None:
689 689 levels = len(self.levels)
690 690 self._headerlines = min(self.maxheaders, levels)
691 691 self._firstheaderline = levels-self._headerlines
692 692
693 693 def getstylehere(self, style):
694 694 """
695 695 Return a style for displaying the original style ``style``
696 696 in the row the cursor is on.
697 697 """
698 698 return astyle.Style(style.fg, style.bg, style.attrs | astyle.A_BOLD)
699 699
700 700 def report(self, msg):
701 701 """
702 702 Store the message ``msg`` for display below the footer line. This
703 703 will be displayed as soon as the screen is redrawn.
704 704 """
705 705 self._report = msg
706 706
707 707 def enter(self, item, mode, *attrs):
708 708 """
709 709 Enter the object ``item`` in the mode ``mode``. If ``attrs`` is
710 710 specified, it will be used as a fixed list of attributes to display.
711 711 """
712 712 try:
713 713 iterator = ipipe.xiter(item, mode)
714 714 except (KeyboardInterrupt, SystemExit):
715 715 raise
716 716 except Exception, exc:
717 717 curses.beep()
718 718 self.report(exc)
719 719 else:
720 720 self._calcheaderlines(len(self.levels)+1)
721 721 level = _BrowserLevel(
722 722 self,
723 723 item,
724 724 iterator,
725 725 self.scrsizey-1-self._headerlines-2,
726 726 *attrs
727 727 )
728 728 self.levels.append(level)
729 729
730 730 def startkeyboardinput(self, mode):
731 731 """
732 732 Enter mode ``mode``, which requires keyboard input.
733 733 """
734 734 self.mode = mode
735 735 self.keyboardinput = ""
736 736 self.cursorpos = 0
737 737
738 738 def executekeyboardinput(self, mode):
739 739 exe = getattr(self, "exe_%s" % mode, None)
740 740 if exe is not None:
741 741 exe()
742 742 self.mode = "default"
743 743
744 744 def keylabel(self, keycode):
745 745 """
746 746 Return a pretty name for the ``curses`` key ``keycode`` (used in the
747 747 help screen and in reports about unassigned keys).
748 748 """
749 749 if keycode <= 0xff:
750 750 specialsnames = {
751 751 ord("\n"): "RETURN",
752 752 ord(" "): "SPACE",
753 753 ord("\t"): "TAB",
754 754 ord("\x7f"): "DELETE",
755 755 ord("\x08"): "BACKSPACE",
756 756 }
757 757 if keycode in specialsnames:
758 758 return specialsnames[keycode]
759 759 return repr(chr(keycode))
760 760 for name in dir(curses):
761 761 if name.startswith("KEY_") and getattr(curses, name) == keycode:
762 762 return name
763 763 return str(keycode)
764 764
765 765 def beep(self, force=False):
766 766 if force or self._dobeep:
767 767 curses.beep()
768 768 # don't beep again (as long as the same key is pressed)
769 769 self._dobeep = False
770 770
771 771 def cmd_quit(self):
772 772 self.returnvalue = None
773 773 return True
774 774
775 775 def cmd_up(self):
776 776 level = self.levels[-1]
777 777 self.report("up")
778 778 level.moveto(level.curx, level.cury-self.stepy)
779 779
780 780 def cmd_down(self):
781 781 level = self.levels[-1]
782 782 self.report("down")
783 783 level.moveto(level.curx, level.cury+self.stepy)
784 784
785 785 def cmd_pageup(self):
786 786 level = self.levels[-1]
787 787 self.report("page up")
788 788 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
789 789
790 790 def cmd_pagedown(self):
791 791 level = self.levels[-1]
792 792 self.report("page down")
793 793 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
794 794
795 795 def cmd_left(self):
796 796 level = self.levels[-1]
797 797 self.report("left")
798 798 level.moveto(level.curx-self.stepx, level.cury)
799 799
800 800 def cmd_right(self):
801 801 level = self.levels[-1]
802 802 self.report("right")
803 803 level.moveto(level.curx+self.stepx, level.cury)
804 804
805 805 def cmd_home(self):
806 806 level = self.levels[-1]
807 807 self.report("home")
808 808 level.moveto(0, level.cury)
809 809
810 810 def cmd_end(self):
811 811 level = self.levels[-1]
812 812 self.report("end")
813 813 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
814 814
815 815 def cmd_prevattr(self):
816 816 level = self.levels[-1]
817 817 if level.displayattr[0] is None or level.displayattr[0] == 0:
818 818 self.beep()
819 819 else:
820 820 self.report("prevattr")
821 821 pos = 0
822 822 for (i, attrname) in enumerate(level.displayattrs):
823 823 if i == level.displayattr[0]-1:
824 824 break
825 825 pos += level.colwidths[attrname] + 1
826 826 level.moveto(pos, level.cury)
827 827
828 828 def cmd_nextattr(self):
829 829 level = self.levels[-1]
830 830 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
831 831 self.beep()
832 832 else:
833 833 self.report("nextattr")
834 834 pos = 0
835 835 for (i, attrname) in enumerate(level.displayattrs):
836 836 if i == level.displayattr[0]+1:
837 837 break
838 838 pos += level.colwidths[attrname] + 1
839 839 level.moveto(pos, level.cury)
840 840
841 841 def cmd_pick(self):
842 842 level = self.levels[-1]
843 843 self.returnvalue = level.items[level.cury].item
844 844 return True
845 845
846 846 def cmd_pickattr(self):
847 847 level = self.levels[-1]
848 848 attrname = level.displayattr[1]
849 if attrname is ipipe._default:
849 if attrname is ipipe.noitem:
850 850 curses.beep()
851 851 self.report(AttributeError(ipipe._attrname(attrname)))
852 852 return
853 853 attr = ipipe._getattr(level.items[level.cury].item, attrname)
854 if attr is ipipe._default:
854 if attr is ipipe.noitem:
855 855 curses.beep()
856 856 self.report(AttributeError(ipipe._attrname(attrname)))
857 857 else:
858 858 self.returnvalue = attr
859 859 return True
860 860
861 861 def cmd_pickallattrs(self):
862 862 level = self.levels[-1]
863 863 attrname = level.displayattr[1]
864 if attrname is ipipe._default:
864 if attrname is ipipe.noitem:
865 865 curses.beep()
866 866 self.report(AttributeError(ipipe._attrname(attrname)))
867 867 return
868 868 result = []
869 869 for cache in level.items:
870 870 attr = ipipe._getattr(cache.item, attrname)
871 if attr is not ipipe._default:
871 if attr is not ipipe.noitem:
872 872 result.append(attr)
873 873 self.returnvalue = result
874 874 return True
875 875
876 876 def cmd_pickmarked(self):
877 877 level = self.levels[-1]
878 878 self.returnvalue = [cache.item for cache in level.items if cache.marked]
879 879 return True
880 880
881 881 def cmd_pickmarkedattr(self):
882 882 level = self.levels[-1]
883 883 attrname = level.displayattr[1]
884 if attrname is ipipe._default:
884 if attrname is ipipe.noitem:
885 885 curses.beep()
886 886 self.report(AttributeError(ipipe._attrname(attrname)))
887 887 return
888 888 result = []
889 889 for cache in level.items:
890 890 if cache.marked:
891 891 attr = ipipe._getattr(cache.item, attrname)
892 if attr is not ipipe._default:
892 if attr is not ipipe.noitem:
893 893 result.append(attr)
894 894 self.returnvalue = result
895 895 return True
896 896
897 897 def cmd_markrange(self):
898 898 level = self.levels[-1]
899 899 self.report("markrange")
900 900 start = None
901 901 if level.items:
902 902 for i in xrange(level.cury, -1, -1):
903 903 if level.items[i].marked:
904 904 start = i
905 905 break
906 906 if start is None:
907 907 self.report(CommandError("no mark before cursor"))
908 908 curses.beep()
909 909 else:
910 910 for i in xrange(start, level.cury+1):
911 911 cache = level.items[i]
912 912 if not cache.marked:
913 913 cache.marked = True
914 914 level.marked += 1
915 915
916 916 def cmd_enterdefault(self):
917 917 level = self.levels[-1]
918 918 try:
919 919 item = level.items[level.cury].item
920 920 except IndexError:
921 921 self.report(CommandError("No object"))
922 922 curses.beep()
923 923 else:
924 924 self.report("entering object (default mode)...")
925 925 self.enter(item, "default")
926 926
927 927 def cmd_leave(self):
928 928 self.report("leave")
929 929 if len(self.levels) > 1:
930 930 self._calcheaderlines(len(self.levels)-1)
931 931 self.levels.pop(-1)
932 932 else:
933 933 self.report(CommandError("This is the last level"))
934 934 curses.beep()
935 935
936 936 def cmd_enter(self):
937 937 level = self.levels[-1]
938 938 try:
939 939 item = level.items[level.cury].item
940 940 except IndexError:
941 941 self.report(CommandError("No object"))
942 942 curses.beep()
943 943 else:
944 944 self.report("entering object...")
945 945 self.enter(item, None)
946 946
947 947 def cmd_enterattr(self):
948 948 level = self.levels[-1]
949 949 attrname = level.displayattr[1]
950 if attrname is ipipe._default:
950 if attrname is ipipe.noitem:
951 951 curses.beep()
952 952 self.report(AttributeError(ipipe._attrname(attrname)))
953 953 return
954 954 try:
955 955 item = level.items[level.cury].item
956 956 except IndexError:
957 957 self.report(CommandError("No object"))
958 958 curses.beep()
959 959 else:
960 960 attr = ipipe._getattr(item, attrname)
961 if attr is ipipe._default:
961 if attr is ipipe.noitem:
962 962 self.report(AttributeError(ipipe._attrname(attrname)))
963 963 else:
964 964 self.report("entering object attribute %s..." % ipipe._attrname(attrname))
965 965 self.enter(attr, None)
966 966
967 967 def cmd_detail(self):
968 968 level = self.levels[-1]
969 969 try:
970 970 item = level.items[level.cury].item
971 971 except IndexError:
972 972 self.report(CommandError("No object"))
973 973 curses.beep()
974 974 else:
975 975 self.report("entering detail view for object...")
976 976 self.enter(item, "detail")
977 977
978 978 def cmd_detailattr(self):
979 979 level = self.levels[-1]
980 980 attrname = level.displayattr[1]
981 if attrname is ipipe._default:
981 if attrname is ipipe.noitem:
982 982 curses.beep()
983 983 self.report(AttributeError(ipipe._attrname(attrname)))
984 984 return
985 985 try:
986 986 item = level.items[level.cury].item
987 987 except IndexError:
988 988 self.report(CommandError("No object"))
989 989 curses.beep()
990 990 else:
991 991 attr = ipipe._getattr(item, attrname)
992 if attr is ipipe._default:
992 if attr is ipipe.noitem:
993 993 self.report(AttributeError(ipipe._attrname(attrname)))
994 994 else:
995 995 self.report("entering detail view for attribute...")
996 996 self.enter(attr, "detail")
997 997
998 998 def cmd_tooglemark(self):
999 999 level = self.levels[-1]
1000 1000 self.report("toggle mark")
1001 1001 try:
1002 1002 item = level.items[level.cury]
1003 1003 except IndexError: # no items?
1004 1004 pass
1005 1005 else:
1006 1006 if item.marked:
1007 1007 item.marked = False
1008 1008 level.marked -= 1
1009 1009 else:
1010 1010 item.marked = True
1011 1011 level.marked += 1
1012 1012
1013 1013 def cmd_sortattrasc(self):
1014 1014 level = self.levels[-1]
1015 1015 attrname = level.displayattr[1]
1016 if attrname is ipipe._default:
1016 if attrname is ipipe.noitem:
1017 1017 curses.beep()
1018 1018 self.report(AttributeError(ipipe._attrname(attrname)))
1019 1019 return
1020 1020 self.report("sort by %s (ascending)" % ipipe._attrname(attrname))
1021 1021 def key(item):
1022 1022 try:
1023 1023 return ipipe._getattr(item, attrname, None)
1024 1024 except (KeyboardInterrupt, SystemExit):
1025 1025 raise
1026 1026 except Exception:
1027 1027 return None
1028 1028 level.sort(key)
1029 1029
1030 1030 def cmd_sortattrdesc(self):
1031 1031 level = self.levels[-1]
1032 1032 attrname = level.displayattr[1]
1033 if attrname is ipipe._default:
1033 if attrname is ipipe.noitem:
1034 1034 curses.beep()
1035 1035 self.report(AttributeError(ipipe._attrname(attrname)))
1036 1036 return
1037 1037 self.report("sort by %s (descending)" % ipipe._attrname(attrname))
1038 1038 def key(item):
1039 1039 try:
1040 1040 return ipipe._getattr(item, attrname, None)
1041 1041 except (KeyboardInterrupt, SystemExit):
1042 1042 raise
1043 1043 except Exception:
1044 1044 return None
1045 1045 level.sort(key, reverse=True)
1046 1046
1047 1047 def cmd_goto(self):
1048 1048 self.startkeyboardinput("goto")
1049 1049
1050 1050 def exe_goto(self):
1051 1051 level = self.levels[-1]
1052 1052 if self.keyboardinput:
1053 1053 level.moveto(level.curx, int(self.keyboardinput))
1054 1054
1055 1055 def cmd_find(self):
1056 1056 self.startkeyboardinput("find")
1057 1057
1058 1058 def exe_find(self):
1059 1059 level = self.levels[-1]
1060 1060 if self.keyboardinput:
1061 1061 while True:
1062 1062 cury = level.cury
1063 1063 level.moveto(level.curx, cury+1)
1064 1064 if cury == level.cury:
1065 1065 curses.beep()
1066 1066 break
1067 1067 item = level.items[level.cury].item
1068 1068 try:
1069 1069 if eval(self.keyboardinput, globals(), ipipe.AttrNamespace(item)):
1070 1070 break
1071 1071 except (KeyboardInterrupt, SystemExit):
1072 1072 raise
1073 1073 except Exception, exc:
1074 1074 self.report(exc)
1075 1075 curses.beep()
1076 1076 break # break on error
1077 1077
1078 1078 def cmd_findbackwards(self):
1079 1079 self.startkeyboardinput("findbackwards")
1080 1080
1081 1081 def exe_findbackwards(self):
1082 1082 level = self.levels[-1]
1083 1083 if self.keyboardinput:
1084 1084 while level.cury:
1085 1085 level.moveto(level.curx, level.cury-1)
1086 1086 item = level.items[level.cury].item
1087 1087 try:
1088 1088 if eval(self.keyboardinput, globals(), ipipe.AttrNamespace(item)):
1089 1089 break
1090 1090 except (KeyboardInterrupt, SystemExit):
1091 1091 raise
1092 1092 except Exception, exc:
1093 1093 self.report(exc)
1094 1094 curses.beep()
1095 1095 break # break on error
1096 1096 else:
1097 1097 curses.beep()
1098 1098
1099 1099 def cmd_help(self):
1100 1100 """
1101 1101 The help command
1102 1102 """
1103 1103 for level in self.levels:
1104 1104 if isinstance(level.input, _BrowserHelp):
1105 1105 curses.beep()
1106 1106 self.report(CommandError("help already active"))
1107 1107 return
1108 1108
1109 1109 self.enter(_BrowserHelp(self), "default")
1110 1110
1111 1111 def _dodisplay(self, scr):
1112 1112 """
1113 1113 This method is the workhorse of the browser. It handles screen
1114 1114 drawing and the keyboard.
1115 1115 """
1116 1116 self.scr = scr
1117 1117 curses.halfdelay(1)
1118 1118 footery = 2
1119 1119
1120 1120 keys = []
1121 1121 for (key, cmd) in self.keymap.iteritems():
1122 1122 if cmd == "quit":
1123 1123 keys.append("%s=%s" % (self.keylabel(key), cmd))
1124 1124 for (key, cmd) in self.keymap.iteritems():
1125 1125 if cmd == "help":
1126 1126 keys.append("%s=%s" % (self.keylabel(key), cmd))
1127 1127 helpmsg = " | %s" % " ".join(keys)
1128 1128
1129 1129 scr.clear()
1130 1130 msg = "Fetching first batch of objects..."
1131 1131 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1132 1132 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1133 1133 scr.refresh()
1134 1134
1135 1135 lastc = -1
1136 1136
1137 1137 self.levels = []
1138 1138 # enter the first level
1139 1139 self.enter(self.input, ipipe.xiter(self.input, "default"), *self.attrs)
1140 1140
1141 1141 self._calcheaderlines(None)
1142 1142
1143 1143 while True:
1144 1144 level = self.levels[-1]
1145 1145 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1146 1146 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1147 1147
1148 1148 # Paint object header
1149 1149 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1150 1150 lv = self.levels[i]
1151 1151 posx = 0
1152 1152 posy = i-self._firstheaderline
1153 1153 endx = self.scrsizex
1154 1154 if i: # not the first level
1155 1155 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1156 1156 if not self.levels[i-1].exhausted:
1157 1157 msg += "+"
1158 1158 msg += ") "
1159 1159 endx -= len(msg)+1
1160 1160 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1161 1161 for (style, text) in lv.header:
1162 1162 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1163 1163 if posx >= endx:
1164 1164 break
1165 1165 if i:
1166 1166 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1167 1167 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1168 1168
1169 1169 if not level.items:
1170 1170 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1171 1171 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1172 1172 scr.clrtobot()
1173 1173 else:
1174 1174 # Paint column headers
1175 1175 scr.move(self._headerlines, 0)
1176 1176 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1177 1177 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1178 1178 begx = level.numbersizex+3
1179 1179 posx = begx-level.datastartx
1180 1180 for attrname in level.displayattrs:
1181 1181 strattrname = ipipe._attrname(attrname)
1182 1182 cwidth = level.colwidths[attrname]
1183 1183 header = strattrname.ljust(cwidth)
1184 1184 if attrname == level.displayattr[1]:
1185 1185 style = self.style_colheaderhere
1186 1186 else:
1187 1187 style = self.style_colheader
1188 1188 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1189 1189 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1190 1190 if posx >= self.scrsizex:
1191 1191 break
1192 1192 else:
1193 1193 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1194 1194
1195 1195 # Paint rows
1196 1196 posy = self._headerlines+1+level.datastarty
1197 1197 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1198 1198 cache = level.items[i]
1199 1199 if i == level.cury:
1200 1200 style = self.style_numberhere
1201 1201 else:
1202 1202 style = self.style_number
1203 1203
1204 1204 posy = self._headerlines+1+i-level.datastarty
1205 1205 posx = begx-level.datastartx
1206 1206
1207 1207 scr.move(posy, 0)
1208 1208 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1209 1209 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1210 1210
1211 1211 for attrname in level.displayattrs:
1212 1212 cwidth = level.colwidths[attrname]
1213 1213 try:
1214 1214 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1215 1215 except KeyError:
1216 1216 align = 2
1217 1217 style = astyle.style_nodata
1218 1218 padstyle = self.style_datapad
1219 1219 sepstyle = self.style_sep
1220 1220 if i == level.cury:
1221 1221 padstyle = self.getstylehere(padstyle)
1222 1222 sepstyle = self.getstylehere(sepstyle)
1223 1223 if align == 2:
1224 1224 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1225 1225 else:
1226 1226 if align == 1:
1227 1227 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1228 1228 elif align == 0:
1229 1229 pad1 = (cwidth-length)//2
1230 1230 pad2 = cwidth-length-len(pad1)
1231 1231 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1232 1232 for (style, text) in parts:
1233 1233 if i == level.cury:
1234 1234 style = self.getstylehere(style)
1235 1235 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1236 1236 if posx >= self.scrsizex:
1237 1237 break
1238 1238 if align == -1:
1239 1239 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1240 1240 elif align == 0:
1241 1241 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1242 1242 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1243 1243 else:
1244 1244 scr.clrtoeol()
1245 1245
1246 1246 # Add blank row headers for the rest of the screen
1247 1247 for posy in xrange(posy+1, self.scrsizey-2):
1248 1248 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1249 1249 scr.clrtoeol()
1250 1250
1251 1251 posy = self.scrsizey-footery
1252 1252 # Display footer
1253 1253 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1254 1254
1255 1255 if level.exhausted:
1256 1256 flag = ""
1257 1257 else:
1258 1258 flag = "+"
1259 1259
1260 1260 endx = self.scrsizex-len(helpmsg)-1
1261 1261 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1262 1262
1263 1263 posx = 0
1264 1264 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1265 1265 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1266 1266 try:
1267 1267 item = level.items[level.cury].item
1268 1268 except IndexError: # empty
1269 1269 pass
1270 1270 else:
1271 1271 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1272 1272 if not isinstance(nostyle, int):
1273 1273 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1274 1274 if posx >= endx:
1275 1275 break
1276 1276
1277 1277 attrstyle = [(astyle.style_default, "no attribute")]
1278 1278 attrname = level.displayattr[1]
1279 if attrname is not ipipe._default and attrname is not None:
1279 if attrname is not ipipe.noitem and attrname is not None:
1280 1280 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1281 1281 posx += self.addstr(posy, posx, 0, endx, ipipe._attrname(attrname), self.style_footer)
1282 1282 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1283 1283 try:
1284 1284 attr = ipipe._getattr(item, attrname)
1285 1285 except (SystemExit, KeyboardInterrupt):
1286 1286 raise
1287 1287 except Exception, exc:
1288 1288 attr = exc
1289 if attr is not ipipe._default:
1289 if attr is not ipipe.noitem:
1290 1290 attrstyle = ipipe.xrepr(attr, "footer")
1291 1291 for (nostyle, text) in attrstyle:
1292 1292 if not isinstance(nostyle, int):
1293 1293 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1294 1294 if posx >= endx:
1295 1295 break
1296 1296
1297 1297 try:
1298 1298 # Display input prompt
1299 1299 if self.mode in self.prompts:
1300 1300 scr.addstr(self.scrsizey-1, 0,
1301 1301 self.prompts[self.mode] + self.keyboardinput,
1302 1302 self.getstyle(astyle.style_default))
1303 1303 # Display report
1304 1304 else:
1305 1305 if self._report is not None:
1306 1306 if isinstance(self._report, Exception):
1307 1307 style = self.getstyle(astyle.style_error)
1308 1308 if self._report.__class__.__module__ == "exceptions":
1309 1309 msg = "%s: %s" % \
1310 1310 (self._report.__class__.__name__, self._report)
1311 1311 else:
1312 1312 msg = "%s.%s: %s" % \
1313 1313 (self._report.__class__.__module__,
1314 1314 self._report.__class__.__name__, self._report)
1315 1315 else:
1316 1316 style = self.getstyle(self.style_report)
1317 1317 msg = self._report
1318 1318 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1319 1319 self._report = None
1320 1320 else:
1321 1321 scr.move(self.scrsizey-1, 0)
1322 1322 except curses.error:
1323 1323 # Protect against error from writing to the last line
1324 1324 pass
1325 1325 scr.clrtoeol()
1326 1326
1327 1327 # Position cursor
1328 1328 if self.mode in self.prompts:
1329 1329 scr.move(self.scrsizey-1, len(self.prompts[self.mode])+self.cursorpos)
1330 1330 else:
1331 1331 scr.move(
1332 1332 1+self._headerlines+level.cury-level.datastarty,
1333 1333 level.numbersizex+3+level.curx-level.datastartx
1334 1334 )
1335 1335 scr.refresh()
1336 1336
1337 1337 # Check keyboard
1338 1338 while True:
1339 1339 c = scr.getch()
1340 1340 if self.mode in self.prompts:
1341 1341 if c in (8, 127, curses.KEY_BACKSPACE):
1342 1342 if self.cursorpos:
1343 1343 self.keyboardinput = self.keyboardinput[:self.cursorpos-1] + self.keyboardinput[self.cursorpos:]
1344 1344 self.cursorpos -= 1
1345 1345 break
1346 1346 else:
1347 1347 curses.beep()
1348 1348 elif c == curses.KEY_LEFT:
1349 1349 if self.cursorpos:
1350 1350 self.cursorpos -= 1
1351 1351 break
1352 1352 else:
1353 1353 curses.beep()
1354 1354 elif c == curses.KEY_RIGHT:
1355 1355 if self.cursorpos < len(self.keyboardinput):
1356 1356 self.cursorpos += 1
1357 1357 break
1358 1358 else:
1359 1359 curses.beep()
1360 1360 elif c in (curses.KEY_UP, curses.KEY_DOWN): # cancel
1361 1361 self.mode = "default"
1362 1362 break
1363 1363 elif c == ord("\n"):
1364 1364 self.executekeyboardinput(self.mode)
1365 1365 break
1366 1366 elif c != -1:
1367 1367 try:
1368 1368 c = chr(c)
1369 1369 except ValueError:
1370 1370 curses.beep()
1371 1371 else:
1372 1372 if (self.mode == "goto" and not "0" <= c <= "9"):
1373 1373 curses.beep()
1374 1374 else:
1375 1375 self.keyboardinput = self.keyboardinput[:self.cursorpos] + c + self.keyboardinput[self.cursorpos:]
1376 1376 self.cursorpos += 1
1377 1377 break # Redisplay
1378 1378 else:
1379 1379 # if no key is pressed slow down and beep again
1380 1380 if c == -1:
1381 1381 self.stepx = 1.
1382 1382 self.stepy = 1.
1383 1383 self._dobeep = True
1384 1384 else:
1385 1385 # if a different key was pressed slow down and beep too
1386 1386 if c != lastc:
1387 1387 lastc = c
1388 1388 self.stepx = 1.
1389 1389 self.stepy = 1.
1390 1390 self._dobeep = True
1391 1391 cmdname = self.keymap.get(c, None)
1392 1392 if cmdname is None:
1393 1393 self.report(
1394 1394 UnassignedKeyError("Unassigned key %s" %
1395 1395 self.keylabel(c)))
1396 1396 else:
1397 1397 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1398 1398 if cmdfunc is None:
1399 1399 self.report(
1400 1400 UnknownCommandError("Unknown command %r" %
1401 1401 (cmdname,)))
1402 1402 elif cmdfunc():
1403 1403 returnvalue = self.returnvalue
1404 1404 self.returnvalue = None
1405 1405 return returnvalue
1406 1406 self.stepx = self.nextstepx(self.stepx)
1407 1407 self.stepy = self.nextstepy(self.stepy)
1408 1408 curses.flushinp() # get rid of type ahead
1409 1409 break # Redisplay
1410 1410 self.scr = None
1411 1411
1412 1412 def display(self):
1413 1413 return curses.wrapper(self._dodisplay)
@@ -1,1820 +1,1820 b''
1 1 # -*- coding: iso-8859-1 -*-
2 2
3 3 """
4 4 ``ipipe`` provides classes to be used in an interactive Python session. Doing a
5 5 ``from ipipe import *`` is the preferred way to do this. The name of all
6 6 objects imported this way starts with ``i`` to minimize collisions.
7 7
8 8 ``ipipe`` supports "pipeline expressions", which is something resembling Unix
9 9 pipes. An example is:
10 10
11 11 >>> ienv | isort("key.lower()")
12 12
13 13 This gives a listing of all environment variables sorted by name.
14 14
15 15
16 16 There are three types of objects in a pipeline expression:
17 17
18 18 * ``Table``s: These objects produce items. Examples are ``ls`` (listing the
19 19 current directory, ``ienv`` (listing environment variables), ``ipwd`` (listing
20 20 user account) and ``igrp`` (listing user groups). A ``Table`` must be the
21 21 first object in a pipe expression.
22 22
23 23 * ``Pipe``s: These objects sit in the middle of a pipe expression. They
24 24 transform the input in some way (e.g. filtering or sorting it). Examples are:
25 25 ``ifilter`` (which filters the input pipe), ``isort`` (which sorts the input
26 26 pipe) and ``ieval`` (which evaluates a function or expression for each object
27 27 in the input pipe).
28 28
29 29 * ``Display``s: These objects can be put as the last object in a pipeline
30 30 expression. There are responsible for displaying the result of the pipeline
31 31 expression. If a pipeline expression doesn't end in a display object a default
32 32 display objects will be used. One example is ``browse`` which is a ``curses``
33 33 based browser.
34 34
35 35
36 36 Adding support for pipeline expressions to your own objects can be done through
37 37 three extensions points (all of them optional):
38 38
39 39 * An object that will be displayed as a row by a ``Display`` object should
40 40 implement the method ``__xattrs__(self, mode)``. This method must return a
41 41 sequence of attribute names. This sequence may also contain integers, which
42 42 will be treated as sequence indizes. Also supported is ``None``, which uses
43 43 the object itself and callables which will be called with the object as the
44 44 an argument. If ``__xattrs__()`` isn't implemented ``(None,)`` will be used as
45 45 the attribute sequence (i.e. the object itself (it's ``repr()`` format) will
46 46 be being displayed. The global function ``xattrs()`` implements this
47 47 functionality.
48 48
49 49 * When an object ``foo`` is displayed in the header, footer or table cell of the
50 50 browser ``foo.__xrepr__(mode)`` is called. Mode can be ``"header"`` or
51 51 ``"footer"`` for the header or footer line and ``"cell"`` for a table cell.
52 52 ``__xrepr__()```must return an iterable (e.g. by being a generator) which
53 53 produces the following items: The first item should be a tuple containing
54 54 the alignment (-1 left aligned, 0 centered and 1 right aligned) and whether
55 55 the complete output must be displayed or if the browser is allowed to stop
56 56 output after enough text has been produced (e.g. a syntax highlighted text
57 57 line would use ``True``, but for a large data structure (i.e. a nested list,
58 58 tuple or dictionary) ``False`` would be used). The other output ``__xrepr__()``
59 59 may produce is tuples of ``Style```objects and text (which contain the text
60 60 representation of the object; see the ``astyle`` module). If ``__xrepr__()``
61 61 recursively outputs a data structure the function ``xrepr(object, mode)`` can
62 62 be used and ``"default"`` must be passed as the mode in these calls. This in
63 63 turn calls the ``__xrepr__()`` method on ``object`` (or uses ``repr(object)``
64 64 as the string representation if ``__xrepr__()`` doesn't exist).
65 65
66 66 * Objects that can be iterated by ``Pipe``s must implement the method
67 67 ``__xiter__(self, mode)``. ``mode`` can take the following values:
68 68
69 69 - ``"default"``: This is the default value and ist always used by pipeline
70 70 expressions. Other values are only used in the browser.
71 71 - ``None``: This value is passed by the browser. The object must return an
72 72 iterable of ``XMode`` objects describing all modes supported by the object.
73 73 (This should never include ``"default"`` or ``None``).
74 74 - Any other value that the object supports.
75 75
76 76 The global function ``xiter()`` can be called to get such an iterator. If
77 77 the method ``_xiter__`` isn't implemented, ``xiter()`` falls back to
78 78 ``__iter__``. In addition to that, dictionaries and modules receive special
79 79 treatment (returning an iterator over ``(key, value)`` pairs). This makes it
80 80 possible to use dictionaries and modules in pipeline expressions, for example:
81 81
82 82 >>> import sys
83 83 >>> sys | ifilter("isinstance(value, int)") | idump
84 84 key |value
85 85 api_version| 1012
86 86 dllhandle | 503316480
87 87 hexversion | 33817328
88 88 maxint |2147483647
89 89 maxunicode | 65535
90 90 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
91 91 ...
92 92
93 93 Note: The expression strings passed to ``ifilter()`` and ``isort()`` can
94 94 refer to the object to be filtered or sorted via the variable ``_`` and to any
95 95 of the attributes of the object, i.e.:
96 96
97 97 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
98 98
99 99 does the same as
100 100
101 101 >>> sys.modules | ifilter("value is not None") | isort("key.lower()")
102 102
103 103 In addition to expression strings, it's possible to pass callables (taking
104 104 the object as an argument) to ``ifilter()``, ``isort()`` and ``ieval()``:
105 105
106 106 >>> sys | ifilter(lambda _:isinstance(_.value, int)) \
107 107 ... | ieval(lambda _: (_.key, hex(_.value))) | idump
108 108 0 |1
109 109 api_version|0x3f4
110 110 dllhandle |0x1e000000
111 111 hexversion |0x20402f0
112 112 maxint |0x7fffffff
113 113 maxunicode |0xffff
114 114 """
115 115
116 116 import sys, os, os.path, stat, glob, new, csv, datetime, types
117 117 import textwrap, itertools, mimetypes
118 118
119 119 try: # Python 2.3 compatibility
120 120 import collections
121 121 except ImportError:
122 122 deque = list
123 123 else:
124 124 deque = collections.deque
125 125
126 126 try: # Python 2.3 compatibility
127 127 set
128 128 except NameError:
129 129 import sets
130 130 set = sets.Set
131 131
132 132 try: # Python 2.3 compatibility
133 133 sorted
134 134 except NameError:
135 135 def sorted(iterator, key=None, reverse=False):
136 136 items = list(iterator)
137 137 if key is not None:
138 138 items.sort(lambda i1, i2: cmp(key(i1), key(i2)))
139 139 else:
140 140 items.sort()
141 141 if reverse:
142 142 items.reverse()
143 143 return items
144 144
145 145 try:
146 146 import pwd
147 147 except ImportError:
148 148 pwd = None
149 149
150 150 try:
151 151 import grp
152 152 except ImportError:
153 153 grp = None
154 154
155 155 import path
156 156 try:
157 157 from IPython import genutils
158 158 except ImportError:
159 159 pass
160 160
161 161 import astyle
162 162
163 163
164 164 __all__ = [
165 165 "ifile", "ils", "iglob", "iwalk", "ipwdentry", "ipwd", "igrpentry", "igrp",
166 166 "icsv", "ix", "ichain", "isort", "ifilter", "ieval", "ienum", "ienv",
167 167 "idump", "iless"
168 168 ]
169 169
170 170
171 171 os.stat_float_times(True) # enable microseconds
172 172
173 173
174 174 class AttrNamespace(object):
175 175 """
176 176 Helper class that is used for providing a namespace for evaluating
177 177 expressions containing attribute names of an object.
178 178 """
179 179 def __init__(self, wrapped):
180 180 self.wrapped = wrapped
181 181
182 182 def __getitem__(self, name):
183 183 if name == "_":
184 184 return self.wrapped
185 185 try:
186 186 return getattr(self.wrapped, name)
187 187 except AttributeError:
188 188 raise KeyError(name)
189 189
190 190 # Python 2.3 compatibility
191 191 # use eval workaround to find out which names are used in the
192 192 # eval string and put them into the locals. This works for most
193 193 # normal uses case, bizarre ones like accessing the locals()
194 194 # will fail
195 195 try:
196 196 eval("_", None, AttrNamespace(None))
197 197 except TypeError:
198 198 real_eval = eval
199 199 def eval(codestring, _globals, _locals):
200 200 """
201 201 eval(source[, globals[, locals]]) -> value
202 202
203 203 Evaluate the source in the context of globals and locals.
204 204 The source may be a string representing a Python expression
205 205 or a code object as returned by compile().
206 206 The globals must be a dictionary and locals can be any mappping.
207 207
208 208 This function is a workaround for the shortcomings of
209 209 Python 2.3's eval.
210 210 """
211 211
212 212 code = compile(codestring, "_eval", "eval")
213 213 newlocals = {}
214 214 for name in code.co_names:
215 215 try:
216 216 newlocals[name] = _locals[name]
217 217 except KeyError:
218 218 pass
219 219 return real_eval(code, _globals, newlocals)
220 220
221 221
222 _default = object()
222 noitem = object()
223 223
224 def item(iterator, index, default=_default):
224 def item(iterator, index, default=noitem):
225 225 """
226 226 Return the ``index``th item from the iterator ``iterator``.
227 227 ``index`` must be an integer (negative integers are relative to the
228 228 end (i.e. the last item produced by the iterator)).
229 229
230 230 If ``default`` is given, this will be the default value when
231 231 the iterator doesn't contain an item at this position. Otherwise an
232 232 ``IndexError`` will be raised.
233 233
234 234 Note that using this function will partially or totally exhaust the
235 235 iterator.
236 236 """
237 237 i = index
238 238 if i>=0:
239 239 for item in iterator:
240 240 if not i:
241 241 return item
242 242 i -= 1
243 243 else:
244 244 i = -index
245 245 cache = deque()
246 246 for item in iterator:
247 247 cache.append(item)
248 248 if len(cache)>i:
249 249 cache.popleft()
250 250 if len(cache)==i:
251 251 return cache.popleft()
252 if default is _default:
252 if default is noitem:
253 253 raise IndexError(index)
254 254 else:
255 255 return default
256 256
257 257
258 258 class Table(object):
259 259 """
260 260 A ``Table`` is an object that produces items (just like a normal Python
261 261 iterator/generator does) and can be used as the first object in a pipeline
262 262 expression. The displayhook will open the default browser for such an object
263 263 (instead of simply printing the ``repr()`` result).
264 264 """
265 265
266 266 # We want to support ``foo`` and ``foo()`` in pipeline expression:
267 267 # So we implement the required operators (``|`` and ``+``) in the metaclass,
268 268 # instantiate the class and forward the operator to the instance
269 269 class __metaclass__(type):
270 270 def __iter__(self):
271 271 return iter(self())
272 272
273 273 def __or__(self, other):
274 274 return self() | other
275 275
276 276 def __add__(self, other):
277 277 return self() + other
278 278
279 279 def __radd__(self, other):
280 280 return other + self()
281 281
282 282 def __getitem__(self, index):
283 283 return self()[index]
284 284
285 285 def __getitem__(self, index):
286 286 return item(self, index)
287 287
288 288 def __contains__(self, item):
289 289 for haveitem in self:
290 290 if item == haveitem:
291 291 return True
292 292 return False
293 293
294 294 def __or__(self, other):
295 295 # autoinstantiate right hand side
296 296 if isinstance(other, type) and issubclass(other, (Table, Display)):
297 297 other = other()
298 298 # treat simple strings and functions as ``ieval`` instances
299 299 elif not isinstance(other, Display) and not isinstance(other, Table):
300 300 other = ieval(other)
301 301 # forward operations to the right hand side
302 302 return other.__ror__(self)
303 303
304 304 def __add__(self, other):
305 305 # autoinstantiate right hand side
306 306 if isinstance(other, type) and issubclass(other, Table):
307 307 other = other()
308 308 return ichain(self, other)
309 309
310 310 def __radd__(self, other):
311 311 # autoinstantiate left hand side
312 312 if isinstance(other, type) and issubclass(other, Table):
313 313 other = other()
314 314 return ichain(other, self)
315 315
316 316 def __iter__(self):
317 317 return xiter(self, "default")
318 318
319 319
320 320 class Pipe(Table):
321 321 """
322 322 A ``Pipe`` is an object that can be used in a pipeline expression. It
323 323 processes the objects it gets from its input ``Table``/``Pipe``. Note that
324 324 a ``Pipe`` object can't be used as the first object in a pipeline
325 325 expression, as it doesn't produces items itself.
326 326 """
327 327 class __metaclass__(Table.__metaclass__):
328 328 def __ror__(self, input):
329 329 return input | self()
330 330
331 331 def __ror__(self, input):
332 332 # autoinstantiate left hand side
333 333 if isinstance(input, type) and issubclass(input, Table):
334 334 input = input()
335 335 self.input = input
336 336 return self
337 337
338 338
339 def _getattr(obj, name, default=_default):
339 def _getattr(obj, name, default=noitem):
340 340 """
341 341 Internal helper for getting an attribute of an item. If ``name`` is ``None``
342 342 return the object itself. If ``name`` is an integer, use ``__getitem__``
343 343 instead. If the attribute or item does not exist, return ``default``.
344 344 """
345 345 if name is None:
346 346 return obj
347 347 elif isinstance(name, basestring):
348 348 if name.endswith("()"):
349 349 return getattr(obj, name[:-2], default)()
350 350 else:
351 351 return getattr(obj, name, default)
352 352 elif callable(name):
353 353 try:
354 354 return name(obj)
355 355 except AttributeError:
356 356 return default
357 357 else:
358 358 try:
359 359 return obj[name]
360 360 except IndexError:
361 361 return default
362 362
363 363
364 364 def _attrname(name):
365 365 """
366 366 Internal helper that gives a proper name for the attribute ``name``
367 367 (which might be ``None`` or an ``int``).
368 368 """
369 369 if name is None:
370 370 return "_"
371 371 elif isinstance(name, basestring):
372 372 return name
373 373 elif callable(name):
374 374 return getattr(name, "__xname__", name.__name__)
375 375 else:
376 376 return str(name)
377 377
378 378
379 379 def xrepr(item, mode):
380 380 try:
381 381 func = item.__xrepr__
382 382 except AttributeError:
383 383 pass
384 384 else:
385 385 try:
386 386 for x in func(mode):
387 387 yield x
388 388 except (KeyboardInterrupt, SystemExit):
389 389 raise
390 390 except Exception:
391 391 yield (-1, True)
392 392 yield (astyle.style_default, repr(item))
393 393 return
394 394 if item is None:
395 395 yield (-1, True)
396 396 yield (astyle.style_type_none, repr(item))
397 397 elif isinstance(item, bool):
398 398 yield (-1, True)
399 399 yield (astyle.style_type_bool, repr(item))
400 400 elif isinstance(item, str):
401 401 yield (-1, True)
402 402 if mode == "cell":
403 403 yield (astyle.style_default, repr(item.expandtabs(tab))[1:-1])
404 404 else:
405 405 yield (astyle.style_default, repr(item))
406 406 elif isinstance(item, unicode):
407 407 yield (-1, True)
408 408 if mode == "cell":
409 409 yield (astyle.style_default, repr(item.expandtabs(tab))[2:-1])
410 410 else:
411 411 yield (astyle.style_default, repr(item))
412 412 elif isinstance(item, (int, long, float)):
413 413 yield (1, True)
414 414 yield (astyle.style_type_number, repr(item))
415 415 elif isinstance(item, complex):
416 416 yield (-1, True)
417 417 yield (astyle.style_type_number, repr(item))
418 418 elif isinstance(item, datetime.datetime):
419 419 yield (-1, True)
420 420 if mode == "cell":
421 421 # Don't use strftime() here, as this requires year >= 1900
422 422 yield (astyle.style_type_datetime,
423 423 "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
424 424 (item.year, item.month, item.day,
425 425 item.hour, item.minute, item.second,
426 426 item.microsecond),
427 427 )
428 428 else:
429 429 yield (astyle.style_type_datetime, repr(item))
430 430 elif isinstance(item, datetime.date):
431 431 yield (-1, True)
432 432 if mode == "cell":
433 433 yield (astyle.style_type_datetime,
434 434 "%04d-%02d-%02d" % (item.year, item.month, item.day))
435 435 else:
436 436 yield (astyle.style_type_datetime, repr(item))
437 437 elif isinstance(item, datetime.time):
438 438 yield (-1, True)
439 439 if mode == "cell":
440 440 yield (astyle.style_type_datetime,
441 441 "%02d:%02d:%02d.%06d" % \
442 442 (item.hour, item.minute, item.second, item.microsecond))
443 443 else:
444 444 yield (astyle.style_type_datetime, repr(item))
445 445 elif isinstance(item, datetime.timedelta):
446 446 yield (-1, True)
447 447 yield (astyle.style_type_datetime, repr(item))
448 448 elif isinstance(item, Exception):
449 449 yield (-1, True)
450 450 if item.__class__.__module__ == "exceptions":
451 451 classname = item.__class__.__name__
452 452 else:
453 453 classname = "%s.%s" % \
454 454 (item.__class__.__module__, item.__class__.__name__)
455 455 if mode == "header" or mode == "footer":
456 456 yield (astyle.style_error, "%s: %s" % (classname, item))
457 457 else:
458 458 yield (astyle.style_error, classname)
459 459 elif isinstance(item, (list, tuple)):
460 460 yield (-1, False)
461 461 if mode == "header" or mode == "footer":
462 462 if item.__class__.__module__ == "__builtin__":
463 463 classname = item.__class__.__name__
464 464 else:
465 465 classname = "%s.%s" % \
466 466 (item.__class__.__module__,item.__class__.__name__)
467 467 yield (astyle.style_default,
468 468 "<%s object with %d items at 0x%x>" % \
469 469 (classname, len(item), id(item)))
470 470 else:
471 471 if isinstance(item, list):
472 472 yield (astyle.style_default, "[")
473 473 end = "]"
474 474 else:
475 475 yield (astyle.style_default, "(")
476 476 end = ")"
477 477 for (i, subitem) in enumerate(item):
478 478 if i:
479 479 yield (astyle.style_default, ", ")
480 480 for part in xrepr(subitem, "default"):
481 481 yield part
482 482 yield (astyle.style_default, end)
483 483 elif isinstance(item, (dict, types.DictProxyType)):
484 484 yield (-1, False)
485 485 if mode == "header" or mode == "footer":
486 486 if item.__class__.__module__ == "__builtin__":
487 487 classname = item.__class__.__name__
488 488 else:
489 489 classname = "%s.%s" % \
490 490 (item.__class__.__module__,item.__class__.__name__)
491 491 yield (astyle.style_default,
492 492 "<%s object with %d items at 0x%x>" % \
493 493 (classname, len(item), id(item)))
494 494 else:
495 495 if isinstance(item, dict):
496 496 yield (astyle.style_default, "{")
497 497 end = "}"
498 498 else:
499 499 yield (astyle.style_default, "dictproxy((")
500 500 end = "})"
501 501 for (i, (key, value)) in enumerate(item.iteritems()):
502 502 if i:
503 503 yield (astyle.style_default, ", ")
504 504 for part in xrepr(key, "default"):
505 505 yield part
506 506 yield (astyle.style_default, ": ")
507 507 for part in xrepr(value, "default"):
508 508 yield part
509 509 yield (astyle.style_default, end)
510 510 else:
511 511 yield (-1, True)
512 512 yield (astyle.style_default, repr(item))
513 513
514 514
515 515 def xattrs(item, mode):
516 516 try:
517 517 func = item.__xattrs__
518 518 except AttributeError:
519 519 if mode == "detail":
520 520 return dir(item)
521 521 else:
522 522 return (None,)
523 523 else:
524 524 try:
525 525 return func(mode)
526 526 except (KeyboardInterrupt, SystemExit):
527 527 raise
528 528 except Exception:
529 529 return (None,)
530 530
531 531
532 532 def xiter(item, mode):
533 533 if mode == "detail":
534 534 def items():
535 535 for name in xattrs(item, mode):
536 536 yield XAttr(item, name)
537 537 return items()
538 538 try:
539 539 func = item.__xiter__
540 540 except AttributeError:
541 541 if isinstance(item, (dict, types.DictProxyType)):
542 542 def items(item):
543 543 fields = ("key", "value")
544 544 for (key, value) in item.iteritems():
545 545 yield Fields(fields, key=key, value=value)
546 546 return items(item)
547 547 elif isinstance(item, new.module):
548 548 def items(item):
549 549 fields = ("key", "value")
550 550 for key in sorted(item.__dict__):
551 551 yield Fields(fields, key=key, value=getattr(item, key))
552 552 return items(item)
553 553 elif isinstance(item, basestring):
554 554 if not len(item):
555 555 raise ValueError("can't enter empty string")
556 556 lines = item.splitlines()
557 557 if len(lines) <= 1:
558 558 raise ValueError("can't enter one line string")
559 559 return iter(lines)
560 560 return iter(item)
561 561 else:
562 562 return iter(func(mode)) # iter() just to be safe
563 563
564 564
565 565 class ichain(Pipe):
566 566 """
567 567 Chains multiple ``Table``s into one.
568 568 """
569 569
570 570 def __init__(self, *iters):
571 571 self.iters = iters
572 572
573 573 def __xiter__(self, mode):
574 574 return itertools.chain(*self.iters)
575 575
576 576 def __xrepr__(self, mode):
577 577 yield (-1, True)
578 578 if mode == "header" or mode == "footer":
579 579 for (i, item) in enumerate(self.iters):
580 580 if i:
581 581 yield (astyle.style_default, "+")
582 582 if isinstance(item, Pipe):
583 583 yield (astyle.style_default, "(")
584 584 for part in xrepr(item, mode):
585 585 yield part
586 586 if isinstance(item, Pipe):
587 587 yield (astyle.style_default, ")")
588 588 else:
589 589 yield (astyle.style_default, repr(self))
590 590
591 591 def __repr__(self):
592 592 args = ", ".join([repr(it) for it in self.iters])
593 593 return "%s.%s(%s)" % \
594 594 (self.__class__.__module__, self.__class__.__name__, args)
595 595
596 596
597 597 class ifile(path.path):
598 598 """
599 599 file (or directory) object.
600 600 """
601 601
602 602 def __add_(self, other):
603 603 return ifile(path._base(self) + other)
604 604
605 605 def __radd_(self, other):
606 606 return ifile(other + path._base(self))
607 607
608 608 def __div_(self, other):
609 609 return ifile(path.__div__(self, other))
610 610
611 611 def getcwd():
612 612 """ Return the current working directory as a path object. """
613 613 return ifile(path.path.getcwd())
614 614 getcwd = staticmethod(getcwd)
615 615
616 616 def abspath(self):
617 617 return ifile(path.path.abspath(self))
618 618
619 619 def normcase(self):
620 620 return ifile(path.path.normcase(self))
621 621
622 622 def normpath(self):
623 623 return ifile(path.path.normpath(self))
624 624
625 625 def realpath(self):
626 626 return ifile(path.path.realpath(self))
627 627
628 628 def expanduser(self):
629 629 return ifile(path.path.expanduser(self))
630 630
631 631 def expandvars(self):
632 632 return ifile(path.path.expandvars(self))
633 633
634 634 def dirname(self):
635 635 return ifile(path.path.dirname(self))
636 636
637 637 parent = property(dirname, None, None, path.path.parent.__doc__)
638 638
639 639 def splitpath(self):
640 640 (parent, child) = path.path.splitpath(self)
641 641 return (ifile(parent), child)
642 642
643 643 def splitdrive(self):
644 644 (drive, rel) = path.path.splitdrive(self)
645 645 return (ifile(drive), rel)
646 646
647 647 def splitext(self):
648 648 (filename, ext) = path.path.splitext(self)
649 649 return (ifile(filename), ext)
650 650
651 651 if hasattr(path.path, "splitunc"):
652 652 def splitunc(self):
653 653 (unc, rest) = path.path.splitunc(self)
654 654 return (ifile(unc), rest)
655 655
656 656 def _get_uncshare(self):
657 657 unc, r = os.path.splitunc(self)
658 658 return ifile(unc)
659 659
660 660 uncshare = property(
661 661 _get_uncshare, None, None,
662 662 """ The UNC mount point for this path.
663 663 This is empty for paths on local drives. """)
664 664
665 665 def joinpath(self, *args):
666 666 return ifile(path.path.joinpath(self, *args))
667 667
668 668 def splitall(self):
669 669 return map(ifile, path.path.splitall(self))
670 670
671 671 def relpath(self):
672 672 return ifile(path.path.relpath(self))
673 673
674 674 def relpathto(self, dest):
675 675 return ifile(path.path.relpathto(self, dest))
676 676
677 677 def listdir(self, pattern=None):
678 678 return [ifile(child) for child in path.path.listdir(self, pattern)]
679 679
680 680 def dirs(self, pattern=None):
681 681 return [ifile(child) for child in path.path.dirs(self, pattern)]
682 682
683 683 def files(self, pattern=None):
684 684 return [ifile(child) for child in path.path.files(self, pattern)]
685 685
686 686 def walk(self, pattern=None):
687 687 for child in path.path.walk(self, pattern):
688 688 yield ifile(child)
689 689
690 690 def walkdirs(self, pattern=None):
691 691 for child in path.path.walkdirs(self, pattern):
692 692 yield ifile(child)
693 693
694 694 def walkfiles(self, pattern=None):
695 695 for child in path.path.walkfiles(self, pattern):
696 696 yield ifile(child)
697 697
698 698 def glob(self, pattern):
699 699 return map(ifile, path.path.glob(self, pattern))
700 700
701 701 if hasattr(os, 'readlink'):
702 702 def readlink(self):
703 703 return ifile(path.path.readlink(self))
704 704
705 705 def readlinkabs(self):
706 706 return ifile(path.path.readlinkabs(self))
707 707
708 708 def getmode(self):
709 709 return self.stat().st_mode
710 710 mode = property(getmode, None, None, "Access mode")
711 711
712 712 def gettype(self):
713 713 data = [
714 714 (stat.S_ISREG, "file"),
715 715 (stat.S_ISDIR, "dir"),
716 716 (stat.S_ISCHR, "chardev"),
717 717 (stat.S_ISBLK, "blockdev"),
718 718 (stat.S_ISFIFO, "fifo"),
719 719 (stat.S_ISLNK, "symlink"),
720 720 (stat.S_ISSOCK,"socket"),
721 721 ]
722 722 lstat = self.lstat()
723 723 if lstat is not None:
724 724 types = set([text for (func, text) in data if func(lstat.st_mode)])
725 725 else:
726 726 types = set()
727 727 m = self.mode
728 728 types.update([text for (func, text) in data if func(m)])
729 729 return ", ".join(types)
730 730 type = property(gettype, None, None, "file type (file, directory, link, etc.)")
731 731
732 732 def getmodestr(self):
733 733 m = self.mode
734 734 data = [
735 735 (stat.S_IRUSR, "-r"),
736 736 (stat.S_IWUSR, "-w"),
737 737 (stat.S_IXUSR, "-x"),
738 738 (stat.S_IRGRP, "-r"),
739 739 (stat.S_IWGRP, "-w"),
740 740 (stat.S_IXGRP, "-x"),
741 741 (stat.S_IROTH, "-r"),
742 742 (stat.S_IWOTH, "-w"),
743 743 (stat.S_IXOTH, "-x"),
744 744 ]
745 745 return "".join([text[bool(m&bit)] for (bit, text) in data])
746 746
747 747 modestr = property(getmodestr, None, None, "Access mode as string")
748 748
749 749 def getblocks(self):
750 750 return self.stat().st_blocks
751 751 blocks = property(getblocks, None, None, "File size in blocks")
752 752
753 753 def getblksize(self):
754 754 return self.stat().st_blksize
755 755 blksize = property(getblksize, None, None, "Filesystem block size")
756 756
757 757 def getdev(self):
758 758 return self.stat().st_dev
759 759 dev = property(getdev)
760 760
761 761 def getnlink(self):
762 762 return self.stat().st_nlink
763 763 nlink = property(getnlink, None, None, "Number of links")
764 764
765 765 def getuid(self):
766 766 return self.stat().st_uid
767 767 uid = property(getuid, None, None, "User id of file owner")
768 768
769 769 def getgid(self):
770 770 return self.stat().st_gid
771 771 gid = property(getgid, None, None, "Group id of file owner")
772 772
773 773 def getowner(self):
774 774 stat = self.stat()
775 775 try:
776 776 return pwd.getpwuid(stat.st_uid).pw_name
777 777 except KeyError:
778 778 return stat.st_uid
779 779 owner = property(getowner, None, None, "Owner name (or id)")
780 780
781 781 def getgroup(self):
782 782 stat = self.stat()
783 783 try:
784 784 return grp.getgrgid(stat.st_gid).gr_name
785 785 except KeyError:
786 786 return stat.st_gid
787 787 group = property(getgroup, None, None, "Group name (or id)")
788 788
789 789 def getadate(self):
790 790 return datetime.datetime.utcfromtimestamp(self.atime)
791 791 adate = property(getadate, None, None, "Access date")
792 792
793 793 def getcdate(self):
794 794 return datetime.datetime.utcfromtimestamp(self.ctime)
795 795 cdate = property(getcdate, None, None, "Creation date")
796 796
797 797 def getmdate(self):
798 798 return datetime.datetime.utcfromtimestamp(self.mtime)
799 799 mdate = property(getmdate, None, None, "Modification date")
800 800
801 801 def getmimetype(self):
802 802 return mimetypes.guess_type(self.basename())[0]
803 803 mimetype = property(getmimetype, None, None, "MIME type")
804 804
805 805 def getencoding(self):
806 806 return mimetypes.guess_type(self.basename())[1]
807 807 encoding = property(getencoding, None, None, "Compression")
808 808
809 809 def __repr__(self):
810 810 return "ifile(%s)" % path._base.__repr__(self)
811 811
812 812 defaultattrs = (None, "type", "size", "modestr", "owner", "group", "mdate")
813 813
814 814 def __xattrs__(self, mode):
815 815 if mode == "detail":
816 816 return (
817 817 "name", "basename()", "abspath()", "realpath()",
818 818 "type", "mode", "modestr", "stat()", "lstat()",
819 819 "uid", "gid", "owner", "group", "dev", "nlink",
820 820 "ctime", "mtime", "atime", "cdate", "mdate", "adate",
821 821 "size", "blocks", "blksize", "isdir()", "islink()",
822 822 "mimetype", "encoding"
823 823 )
824 824 return self.defaultattrs
825 825
826 826 def __xrepr__(self, mode):
827 827 yield (-1, True)
828 828 try:
829 829 if self.isdir():
830 830 name = "idir"
831 831 style = astyle.style_dir
832 832 else:
833 833 name = "ifile"
834 834 style = astyle.style_file
835 835 except IOError:
836 836 name = "ifile"
837 837 style = astyle.style_default
838 838 if mode == "cell" or mode in "header" or mode == "footer":
839 839 abspath = repr(path._base(self.normpath()))
840 840 if abspath.startswith("u"):
841 841 abspath = abspath[2:-1]
842 842 else:
843 843 abspath = abspath[1:-1]
844 844 if mode == "cell":
845 845 yield (style, abspath)
846 846 else:
847 847 yield (style, "%s(%s)" % (name, abspath))
848 848 else:
849 849 yield (style, repr(self))
850 850
851 851 def __xiter__(self, mode):
852 852 if self.isdir():
853 853 yield iparentdir(self / os.pardir)
854 854 for child in sorted(self.listdir()):
855 855 yield child
856 856 else:
857 857 f = self.open("rb")
858 858 for line in f:
859 859 yield line
860 860 f.close()
861 861
862 862
863 863 class iparentdir(ifile):
864 864 def __xrepr__(self, mode):
865 865 yield (-1, True)
866 866 if mode == "cell":
867 867 yield (astyle.style_dir, os.pardir)
868 868 else:
869 869 for part in ifile.__xrepr__(self, mode):
870 870 yield part
871 871
872 872
873 873 class ils(Table):
874 874 """
875 875 List the current (or a specific) directory.
876 876
877 877 Examples:
878 878
879 879 >>> ils
880 880 >>> ils("/usr/local/lib/python2.4")
881 881 >>> ils("~")
882 882 """
883 883 def __init__(self, base=os.curdir):
884 884 self.base = os.path.expanduser(base)
885 885
886 886 def __xiter__(self, mode):
887 887 return xiter(ifile(self.base), mode)
888 888
889 889 def __xrepr__(self, mode):
890 890 return ifile(self.base).__xrepr__(mode)
891 891
892 892 def __repr__(self):
893 893 return "%s.%s(%r)" % \
894 894 (self.__class__.__module__, self.__class__.__name__, self.base)
895 895
896 896
897 897 class iglob(Table):
898 898 """
899 899 List all files and directories matching a specified pattern.
900 900 (See ``glob.glob()`` for more info.).
901 901
902 902 Examples:
903 903
904 904 >>> iglob("*.py")
905 905 """
906 906 def __init__(self, glob):
907 907 self.glob = glob
908 908
909 909 def __xiter__(self, mode):
910 910 for name in glob.glob(self.glob):
911 911 yield ifile(name)
912 912
913 913 def __xrepr__(self, mode):
914 914 yield (-1, True)
915 915 if mode == "header" or mode == "footer" or mode == "cell":
916 916 yield (astyle.style_default,
917 917 "%s(%r)" % (self.__class__.__name__, self.glob))
918 918 else:
919 919 yield (astyle.style_default, repr(self))
920 920
921 921 def __repr__(self):
922 922 return "%s.%s(%r)" % \
923 923 (self.__class__.__module__, self.__class__.__name__, self.glob)
924 924
925 925
926 926 class iwalk(Table):
927 927 """
928 928 List all files and directories in a directory and it's subdirectory.
929 929
930 930 >>> iwalk
931 931 >>> iwalk("/usr/local/lib/python2.4")
932 932 >>> iwalk("~")
933 933 """
934 934 def __init__(self, base=os.curdir, dirs=True, files=True):
935 935 self.base = os.path.expanduser(base)
936 936 self.dirs = dirs
937 937 self.files = files
938 938
939 939 def __xiter__(self, mode):
940 940 for (dirpath, dirnames, filenames) in os.walk(self.base):
941 941 if self.dirs:
942 942 for name in sorted(dirnames):
943 943 yield ifile(os.path.join(dirpath, name))
944 944 if self.files:
945 945 for name in sorted(filenames):
946 946 yield ifile(os.path.join(dirpath, name))
947 947
948 948 def __xrepr__(self, mode):
949 949 yield (-1, True)
950 950 if mode == "header" or mode == "footer" or mode == "cell":
951 951 yield (astyle.style_default,
952 952 "%s(%r)" % (self.__class__.__name__, self.base))
953 953 else:
954 954 yield (astyle.style_default, repr(self))
955 955
956 956 def __repr__(self):
957 957 return "%s.%s(%r)" % \
958 958 (self.__class__.__module__, self.__class__.__name__, self.base)
959 959
960 960
961 961 class ipwdentry(object):
962 962 """
963 963 ``ipwdentry`` objects encapsulate entries in the Unix user account and
964 964 password database.
965 965 """
966 966 def __init__(self, id):
967 967 self._id = id
968 968 self._entry = None
969 969
970 970 def _getentry(self):
971 971 if self._entry is None:
972 972 if isinstance(self._id, basestring):
973 973 self._entry = pwd.getpwnam(self._id)
974 974 else:
975 975 self._entry = pwd.getpwuid(self._id)
976 976 return self._entry
977 977
978 978 def getname(self):
979 979 if isinstance(self._id, basestring):
980 980 return self._id
981 981 else:
982 982 return self._getentry().pw_name
983 983 name = property(getname, None, None, "User name")
984 984
985 985 def getpasswd(self):
986 986 return self._getentry().pw_passwd
987 987 passwd = property(getpasswd, None, None, "Password")
988 988
989 989 def getuid(self):
990 990 if isinstance(self._id, basestring):
991 991 return self._getentry().pw_uid
992 992 else:
993 993 return self._id
994 994 uid = property(getuid, None, None, "User id")
995 995
996 996 def getgid(self):
997 997 return self._getentry().pw_gid
998 998 gid = property(getgid, None, None, "Primary group id")
999 999
1000 1000 def getgroup(self):
1001 1001 return igrpentry(self.gid)
1002 1002 group = property(getgroup, None, None, "Group")
1003 1003
1004 1004 def getgecos(self):
1005 1005 return self._getentry().pw_gecos
1006 1006 gecos = property(getgecos, None, None, "Information (e.g. full user name)")
1007 1007
1008 1008 def getdir(self):
1009 1009 return self._getentry().pw_dir
1010 1010 dir = property(getdir, None, None, "$HOME directory")
1011 1011
1012 1012 def getshell(self):
1013 1013 return self._getentry().pw_shell
1014 1014 shell = property(getshell, None, None, "Login shell")
1015 1015
1016 1016 def __xattrs__(self, mode):
1017 1017 return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell")
1018 1018
1019 1019 def __repr__(self):
1020 1020 return "%s.%s(%r)" % \
1021 1021 (self.__class__.__module__, self.__class__.__name__, self._id)
1022 1022
1023 1023
1024 1024 class ipwd(Table):
1025 1025 """
1026 1026 List all entries in the Unix user account and password database.
1027 1027
1028 1028 Example:
1029 1029
1030 1030 >>> ipwd | isort("uid")
1031 1031 """
1032 1032 def __iter__(self):
1033 1033 for entry in pwd.getpwall():
1034 1034 yield ipwdentry(entry.pw_name)
1035 1035
1036 1036 def __xrepr__(self, mode):
1037 1037 yield (-1, True)
1038 1038 if mode == "header" or mode == "footer" or mode == "cell":
1039 1039 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1040 1040 else:
1041 1041 yield (astyle.style_default, repr(self))
1042 1042
1043 1043
1044 1044 class igrpentry(object):
1045 1045 """
1046 1046 ``igrpentry`` objects encapsulate entries in the Unix group database.
1047 1047 """
1048 1048 def __init__(self, id):
1049 1049 self._id = id
1050 1050 self._entry = None
1051 1051
1052 1052 def _getentry(self):
1053 1053 if self._entry is None:
1054 1054 if isinstance(self._id, basestring):
1055 1055 self._entry = grp.getgrnam(self._id)
1056 1056 else:
1057 1057 self._entry = grp.getgrgid(self._id)
1058 1058 return self._entry
1059 1059
1060 1060 def getname(self):
1061 1061 if isinstance(self._id, basestring):
1062 1062 return self._id
1063 1063 else:
1064 1064 return self._getentry().gr_name
1065 1065 name = property(getname, None, None, "Group name")
1066 1066
1067 1067 def getpasswd(self):
1068 1068 return self._getentry().gr_passwd
1069 1069 passwd = property(getpasswd, None, None, "Password")
1070 1070
1071 1071 def getgid(self):
1072 1072 if isinstance(self._id, basestring):
1073 1073 return self._getentry().gr_gid
1074 1074 else:
1075 1075 return self._id
1076 1076 gid = property(getgid, None, None, "Group id")
1077 1077
1078 1078 def getmem(self):
1079 1079 return self._getentry().gr_mem
1080 1080 mem = property(getmem, None, None, "Members")
1081 1081
1082 1082 def __xattrs__(self, mode):
1083 1083 return ("name", "passwd", "gid", "mem")
1084 1084
1085 1085 def __xrepr__(self, mode):
1086 1086 yield (-1, True)
1087 1087 if mode == "header" or mode == "footer" or mode == "cell":
1088 1088 yield (astyle.style_default, "group ")
1089 1089 try:
1090 1090 yield (astyle.style_default, self.name)
1091 1091 except KeyError:
1092 1092 if isinstance(self._id, basestring):
1093 1093 yield (astyle.style_default, self.name_id)
1094 1094 else:
1095 1095 yield (astyle.style_type_number, str(self._id))
1096 1096 else:
1097 1097 yield (astyle.style_default, repr(self))
1098 1098
1099 1099 def __xiter__(self, mode):
1100 1100 for member in self.mem:
1101 1101 yield ipwdentry(member)
1102 1102
1103 1103 def __repr__(self):
1104 1104 return "%s.%s(%r)" % \
1105 1105 (self.__class__.__module__, self.__class__.__name__, self._id)
1106 1106
1107 1107
1108 1108 class igrp(Table):
1109 1109 """
1110 1110 This ``Table`` lists all entries in the Unix group database.
1111 1111 """
1112 1112 def __xiter__(self, mode):
1113 1113 for entry in grp.getgrall():
1114 1114 yield igrpentry(entry.gr_name)
1115 1115
1116 1116 def __xrepr__(self, mode):
1117 1117 yield (-1, False)
1118 1118 if mode == "header" or mode == "footer":
1119 1119 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1120 1120 else:
1121 1121 yield (astyle.style_default, repr(self))
1122 1122
1123 1123
1124 1124 class Fields(object):
1125 1125 def __init__(self, fieldnames, **fields):
1126 1126 self.__fieldnames = fieldnames
1127 1127 for (key, value) in fields.iteritems():
1128 1128 setattr(self, key, value)
1129 1129
1130 1130 def __xattrs__(self, mode):
1131 1131 return self.__fieldnames
1132 1132
1133 1133 def __xrepr__(self, mode):
1134 1134 yield (-1, False)
1135 1135 if mode == "header" or mode == "cell":
1136 1136 yield (astyle.style_default, self.__class__.__name__)
1137 1137 yield (astyle.style_default, "(")
1138 1138 for (i, f) in enumerate(self.__fieldnames):
1139 1139 if i:
1140 1140 yield (astyle.style_default, ", ")
1141 1141 yield (astyle.style_default, f)
1142 1142 yield (astyle.style_default, "=")
1143 1143 for part in xrepr(getattr(self, f), "default"):
1144 1144 yield part
1145 1145 yield (astyle.style_default, ")")
1146 1146 elif mode == "footer":
1147 1147 yield (astyle.style_default, self.__class__.__name__)
1148 1148 yield (astyle.style_default, "(")
1149 1149 for (i, f) in enumerate(self.__fieldnames):
1150 1150 if i:
1151 1151 yield (astyle.style_default, ", ")
1152 1152 yield (astyle.style_default, f)
1153 1153 yield (astyle.style_default, ")")
1154 1154 else:
1155 1155 yield (astyle.style_default, repr(self))
1156 1156
1157 1157
1158 1158 class FieldTable(Table, list):
1159 1159 def __init__(self, *fields):
1160 1160 Table.__init__(self)
1161 1161 list.__init__(self)
1162 1162 self.fields = fields
1163 1163
1164 1164 def add(self, **fields):
1165 1165 self.append(Fields(self.fields, **fields))
1166 1166
1167 1167 def __xiter__(self, mode):
1168 1168 return list.__iter__(self)
1169 1169
1170 1170 def __xrepr__(self, mode):
1171 1171 yield (-1, False)
1172 1172 if mode == "header" or mode == "footer":
1173 1173 yield (astyle.style_default, self.__class__.__name__)
1174 1174 yield (astyle.style_default, "(")
1175 1175 for (i, f) in enumerate(self.__fieldnames):
1176 1176 if i:
1177 1177 yield (astyle.style_default, ", ")
1178 1178 yield (astyle.style_default, f)
1179 1179 yield (astyle.style_default, ")")
1180 1180 else:
1181 1181 yield (astyle.style_default, repr(self))
1182 1182
1183 1183 def __repr__(self):
1184 1184 return "<%s.%s object with fields=%r at 0x%x>" % \
1185 1185 (self.__class__.__module__, self.__class__.__name__,
1186 1186 ", ".join(map(repr, self.fields)), id(self))
1187 1187
1188 1188
1189 1189 class List(list):
1190 1190 def __xattrs__(self, mode):
1191 1191 return xrange(len(self))
1192 1192
1193 1193 def __xrepr__(self, mode):
1194 1194 yield (-1, False)
1195 1195 if mode == "header" or mode == "cell" or mode == "footer" or mode == "default":
1196 1196 yield (astyle.style_default, self.__class__.__name__)
1197 1197 yield (astyle.style_default, "(")
1198 1198 for (i, item) in enumerate(self):
1199 1199 if i:
1200 1200 yield (astyle.style_default, ", ")
1201 1201 for part in xrepr(item, "default"):
1202 1202 yield part
1203 1203 yield (astyle.style_default, ")")
1204 1204 else:
1205 1205 yield (astyle.style_default, repr(self))
1206 1206
1207 1207
1208 1208 class ienv(Table):
1209 1209 """
1210 1210 List environment variables.
1211 1211
1212 1212 Example:
1213 1213
1214 1214 >>> ienv
1215 1215 """
1216 1216
1217 1217 def __xiter__(self, mode):
1218 1218 fields = ("key", "value")
1219 1219 for (key, value) in os.environ.iteritems():
1220 1220 yield Fields(fields, key=key, value=value)
1221 1221
1222 1222 def __xrepr__(self, mode):
1223 1223 yield (-1, True)
1224 1224 if mode == "header" or mode == "cell":
1225 1225 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1226 1226 else:
1227 1227 yield (astyle.style_default, repr(self))
1228 1228
1229 1229
1230 1230 class icsv(Pipe):
1231 1231 """
1232 1232 This ``Pipe`` lists turn the input (with must be a pipe outputting lines
1233 1233 or an ``ifile``) into lines of CVS columns.
1234 1234 """
1235 1235 def __init__(self, **csvargs):
1236 1236 """
1237 1237 Create an ``icsv`` object. ``cvsargs`` will be passed through as
1238 1238 keyword arguments to ``cvs.reader()``.
1239 1239 """
1240 1240 self.csvargs = csvargs
1241 1241
1242 1242 def __xiter__(self, mode):
1243 1243 input = self.input
1244 1244 if isinstance(input, ifile):
1245 1245 input = input.open("rb")
1246 1246 reader = csv.reader(input, **self.csvargs)
1247 1247 for line in reader:
1248 1248 yield List(line)
1249 1249
1250 1250 def __xrepr__(self, mode):
1251 1251 yield (-1, False)
1252 1252 if mode == "header" or mode == "footer":
1253 1253 input = getattr(self, "input", None)
1254 1254 if input is not None:
1255 1255 for part in xrepr(input, mode):
1256 1256 yield part
1257 1257 yield (astyle.style_default, " | ")
1258 1258 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1259 1259 for (i, (name, value)) in enumerate(self.csvargs.iteritems()):
1260 1260 if i:
1261 1261 yield (astyle.style_default, ", ")
1262 1262 yield (astyle.style_default, name)
1263 1263 yield (astyle.style_default, "=")
1264 1264 for part in xrepr(value, "default"):
1265 1265 yield part
1266 1266 yield (astyle.style_default, ")")
1267 1267 else:
1268 1268 yield (astyle.style_default, repr(self))
1269 1269
1270 1270 def __repr__(self):
1271 1271 args = ", ".join(["%s=%r" % item for item in self.csvargs.iteritems()])
1272 1272 return "<%s.%s %s at 0x%x>" % \
1273 1273 (self.__class__.__module__, self.__class__.__name__, args, id(self))
1274 1274
1275 1275
1276 1276 class ix(Table):
1277 1277 """
1278 1278 Execute a system command and list its output as lines
1279 1279 (similar to ``os.popen()``).
1280 1280
1281 1281 Examples:
1282 1282
1283 1283 >>> ix("ps x")
1284 1284 >>> ix("find .") | ifile
1285 1285 """
1286 1286 def __init__(self, cmd):
1287 1287 self.cmd = cmd
1288 1288 self._pipe = None
1289 1289
1290 1290 def __xiter__(self, mode):
1291 1291 self._pipe = os.popen(self.cmd)
1292 1292 for l in self._pipe:
1293 1293 yield l.rstrip("\r\n")
1294 1294 self._pipe.close()
1295 1295 self._pipe = None
1296 1296
1297 1297 def __del__(self):
1298 1298 if self._pipe is not None and not self._pipe.closed:
1299 1299 self._pipe.close()
1300 1300 self._pipe = None
1301 1301
1302 1302 def __xrepr__(self, mode):
1303 1303 yield (-1, True)
1304 1304 if mode == "header" or mode == "footer":
1305 1305 yield (astyle.style_default,
1306 1306 "%s(%r)" % (self.__class__.__name__, self.cmd))
1307 1307 else:
1308 1308 yield (astyle.style_default, repr(self))
1309 1309
1310 1310 def __repr__(self):
1311 1311 return "%s.%s(%r)" % \
1312 1312 (self.__class__.__module__, self.__class__.__name__, self.cmd)
1313 1313
1314 1314
1315 1315 class ifilter(Pipe):
1316 1316 """
1317 1317 Filter an input pipe. Only objects where an expression evaluates to true
1318 1318 (and doesn't raise an exception) are listed.
1319 1319
1320 1320 Examples:
1321 1321
1322 1322 >>> ils | ifilter("_.isfile() and size>1000")
1323 1323 >>> igrp | ifilter("len(mem)")
1324 1324 >>> sys.modules | ifilter(lambda _:_.value is not None)
1325 1325 """
1326 1326
1327 1327 def __init__(self, expr, errors="raiseifallfail"):
1328 1328 """
1329 1329 Create an ``ifilter`` object. ``expr`` can be a callable or a string
1330 1330 containing an expression. ``errors`` specifies how exception during
1331 1331 evaluation of ``expr`` are handled:
1332 1332
1333 1333 * ``drop``: drop all items that have errors;
1334 1334
1335 1335 * ``keep``: keep all items that have errors;
1336 1336
1337 1337 * ``keeperror``: keep the exception of all items that have errors;
1338 1338
1339 1339 * ``raise``: raise the exception;
1340 1340
1341 1341 * ``raiseifallfail``: raise the first exception if all items have errors;
1342 1342 otherwise drop those with errors (this is the default).
1343 1343 """
1344 1344 self.expr = expr
1345 1345 self.errors = errors
1346 1346
1347 1347 def __xiter__(self, mode):
1348 1348 if callable(self.expr):
1349 1349 def test(item):
1350 1350 return self.expr(item)
1351 1351 else:
1352 1352 def test(item):
1353 1353 return eval(self.expr, globals(), AttrNamespace(item))
1354 1354
1355 1355 ok = 0
1356 1356 exc_info = None
1357 1357 for item in xiter(self.input, mode):
1358 1358 try:
1359 1359 if test(item):
1360 1360 yield item
1361 1361 ok += 1
1362 1362 except (KeyboardInterrupt, SystemExit):
1363 1363 raise
1364 1364 except Exception, exc:
1365 1365 if self.errors == "drop":
1366 1366 pass # Ignore errors
1367 1367 elif self.errors == "keep":
1368 1368 yield item
1369 1369 elif self.errors == "keeperror":
1370 1370 yield exc
1371 1371 elif self.errors == "raise":
1372 1372 raise
1373 1373 elif self.errors == "raiseifallfail":
1374 1374 if exc_info is None:
1375 1375 exc_info = sys.exc_info()
1376 1376 if not ok and exc_info is not None:
1377 1377 raise exc_info[0], exc_info[1], exc_info[2]
1378 1378
1379 1379 def __xrepr__(self, mode):
1380 1380 yield (-1, True)
1381 1381 if mode == "header" or mode == "footer":
1382 1382 input = getattr(self, "input", None)
1383 1383 if input is not None:
1384 1384 for part in xrepr(input, mode):
1385 1385 yield part
1386 1386 yield (astyle.style_default, " | ")
1387 1387 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1388 1388 for part in xrepr(self.expr, "default"):
1389 1389 yield part
1390 1390 yield (astyle.style_default, ")")
1391 1391 else:
1392 1392 yield (astyle.style_default, repr(self))
1393 1393
1394 1394 def __repr__(self):
1395 1395 return "<%s.%s expr=%r at 0x%x>" % \
1396 1396 (self.__class__.__module__, self.__class__.__name__,
1397 1397 self.expr, id(self))
1398 1398
1399 1399
1400 1400 class ieval(Pipe):
1401 1401 """
1402 1402 Evaluate an expression for each object in the input pipe.
1403 1403
1404 1404 Examples:
1405 1405
1406 1406 >>> ils | ieval("_.abspath()")
1407 1407 >>> sys.path | ieval(ifile)
1408 1408 """
1409 1409
1410 1410 def __init__(self, expr, errors="raiseifallfail"):
1411 1411 """
1412 1412 Create an ``ieval`` object. ``expr`` can be a callable or a string
1413 1413 containing an expression. For the meaning of ``errors`` see ``ifilter``.
1414 1414 """
1415 1415 self.expr = expr
1416 1416 self.errors = errors
1417 1417
1418 1418 def __xiter__(self, mode):
1419 1419 if callable(self.expr):
1420 1420 def do(item):
1421 1421 return self.expr(item)
1422 1422 else:
1423 1423 def do(item):
1424 1424 return eval(self.expr, globals(), AttrNamespace(item))
1425 1425
1426 1426 ok = 0
1427 1427 exc_info = None
1428 1428 for item in xiter(self.input, mode):
1429 1429 try:
1430 1430 yield do(item)
1431 1431 except (KeyboardInterrupt, SystemExit):
1432 1432 raise
1433 1433 except Exception, exc:
1434 1434 if self.errors == "drop":
1435 1435 pass # Ignore errors
1436 1436 elif self.errors == "keep":
1437 1437 yield item
1438 1438 elif self.errors == "keeperror":
1439 1439 yield exc
1440 1440 elif self.errors == "raise":
1441 1441 raise
1442 1442 elif self.errors == "raiseifallfail":
1443 1443 if exc_info is None:
1444 1444 exc_info = sys.exc_info()
1445 1445 if not ok and exc_info is not None:
1446 1446 raise exc_info[0], exc_info[1], exc_info[2]
1447 1447
1448 1448 def __xrepr__(self, mode):
1449 1449 yield (-1, True)
1450 1450 if mode == "header" or mode == "footer":
1451 1451 input = getattr(self, "input", None)
1452 1452 if input is not None:
1453 1453 for part in xrepr(input, mode):
1454 1454 yield part
1455 1455 yield (astyle.style_default, " | ")
1456 1456 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1457 1457 for part in xrepr(self.expr, "default"):
1458 1458 yield part
1459 1459 yield (astyle.style_default, ")")
1460 1460 else:
1461 1461 yield (astyle.style_default, repr(self))
1462 1462
1463 1463 def __repr__(self):
1464 1464 return "<%s.%s expr=%r at 0x%x>" % \
1465 1465 (self.__class__.__module__, self.__class__.__name__,
1466 1466 self.expr, id(self))
1467 1467
1468 1468
1469 1469 class ienum(Pipe):
1470 1470 """
1471 1471 Enumerate the input pipe (i.e. wrap each input object in an object
1472 1472 with ``index`` and ``object`` attributes).
1473 1473
1474 1474 Examples:
1475 1475
1476 1476 >>> xrange(20) | ieval("_,_*_") | ienum | ifilter("index % 2 == 0") | ieval("object")
1477 1477 """
1478 1478 def __xiter__(self, mode):
1479 1479 fields = ("index", "object")
1480 1480 for (index, object) in enumerate(xiter(self.input, mode)):
1481 1481 yield Fields(fields, index=index, object=object)
1482 1482
1483 1483
1484 1484 class isort(Pipe):
1485 1485 """
1486 1486 Sorts the input pipe.
1487 1487
1488 1488 Examples:
1489 1489
1490 1490 >>> ils | isort("size")
1491 1491 >>> ils | isort("_.isdir(), _.lower()", reverse=True)
1492 1492 """
1493 1493
1494 1494 def __init__(self, key, reverse=False):
1495 1495 """
1496 1496 Create an ``isort`` object. ``key`` can be a callable or a string
1497 1497 containing an expression. If ``reverse`` is true the sort order will
1498 1498 be reversed.
1499 1499 """
1500 1500 self.key = key
1501 1501 self.reverse = reverse
1502 1502
1503 1503 def __xiter__(self, mode):
1504 1504 if callable(self.key):
1505 1505 items = sorted(
1506 1506 xiter(self.input, mode),
1507 1507 key=self.key,
1508 1508 reverse=self.reverse
1509 1509 )
1510 1510 else:
1511 1511 def key(item):
1512 1512 return eval(self.key, globals(), AttrNamespace(item))
1513 1513 items = sorted(
1514 1514 xiter(self.input, mode),
1515 1515 key=key,
1516 1516 reverse=self.reverse
1517 1517 )
1518 1518 for item in items:
1519 1519 yield item
1520 1520
1521 1521 def __xrepr__(self, mode):
1522 1522 yield (-1, True)
1523 1523 if mode == "header" or mode == "footer":
1524 1524 input = getattr(self, "input", None)
1525 1525 if input is not None:
1526 1526 for part in xrepr(input, mode):
1527 1527 yield part
1528 1528 yield (astyle.style_default, " | ")
1529 1529 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1530 1530 for part in xrepr(self.key, "default"):
1531 1531 yield part
1532 1532 if self.reverse:
1533 1533 yield (astyle.style_default, ", ")
1534 1534 for part in xrepr(True, "default"):
1535 1535 yield part
1536 1536 yield (astyle.style_default, ")")
1537 1537 else:
1538 1538 yield (astyle.style_default, repr(self))
1539 1539
1540 1540 def __repr__(self):
1541 1541 return "<%s.%s key=%r reverse=%r at 0x%x>" % \
1542 1542 (self.__class__.__module__, self.__class__.__name__,
1543 1543 self.key, self.reverse, id(self))
1544 1544
1545 1545
1546 1546 tab = 3 # for expandtabs()
1547 1547
1548 1548 def _format(field):
1549 1549 if isinstance(field, str):
1550 1550 text = repr(field.expandtabs(tab))[1:-1]
1551 1551 elif isinstance(field, unicode):
1552 1552 text = repr(field.expandtabs(tab))[2:-1]
1553 1553 elif isinstance(field, datetime.datetime):
1554 1554 # Don't use strftime() here, as this requires year >= 1900
1555 1555 text = "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
1556 1556 (field.year, field.month, field.day,
1557 1557 field.hour, field.minute, field.second, field.microsecond)
1558 1558 elif isinstance(field, datetime.date):
1559 1559 text = "%04d-%02d-%02d" % (field.year, field.month, field.day)
1560 1560 else:
1561 1561 text = repr(field)
1562 1562 return text
1563 1563
1564 1564
1565 1565 class Display(object):
1566 1566 class __metaclass__(type):
1567 1567 def __ror__(self, input):
1568 1568 return input | self()
1569 1569
1570 1570 def __ror__(self, input):
1571 1571 self.input = input
1572 1572 return self
1573 1573
1574 1574 def display(self):
1575 1575 pass
1576 1576
1577 1577
1578 1578 class iless(Display):
1579 1579 cmd = "less --quit-if-one-screen --LONG-PROMPT --LINE-NUMBERS --chop-long-lines --shift=8 --RAW-CONTROL-CHARS"
1580 1580
1581 1581 def display(self):
1582 1582 try:
1583 1583 pager = os.popen(self.cmd, "w")
1584 1584 try:
1585 1585 for item in xiter(self.input, "default"):
1586 1586 attrs = xattrs(item, "default")
1587 1587 attrs = ["%s=%s" % (a, _format(_getattr(item, a))) for a in attrs]
1588 1588 pager.write(" ".join(attrs))
1589 1589 pager.write("\n")
1590 1590 finally:
1591 1591 pager.close()
1592 1592 except Exception, exc:
1593 1593 print "%s: %s" % (exc.__class__.__name__, str(exc))
1594 1594
1595 1595
1596 1596 def xformat(value, mode, maxlength):
1597 1597 align = None
1598 1598 full = False
1599 1599 width = 0
1600 1600 text = astyle.Text()
1601 1601 for part in xrepr(value, mode):
1602 1602 # part is (alignment, stop)
1603 1603 if isinstance(part[0], int):
1604 1604 # only consider the first occurence
1605 1605 if align is None:
1606 1606 align = part[0]
1607 1607 full = part[1]
1608 1608 # part is (style, text)
1609 1609 else:
1610 1610 text.append(part)
1611 1611 width += len(part[1])
1612 1612 if width >= maxlength and not full:
1613 1613 text.append((astyle.style_ellisis, "..."))
1614 1614 width += 3
1615 1615 break
1616 1616 if align is None: # default to left alignment
1617 1617 align = -1
1618 1618 return (align, width, text)
1619 1619
1620 1620
1621 1621 class idump(Display):
1622 1622 # The approximate maximum length of a column entry
1623 1623 maxattrlength = 200
1624 1624
1625 1625 # Style for column names
1626 1626 style_header = astyle.Style.fromstr("white:black:bold")
1627 1627
1628 1628 def __init__(self, *attrs):
1629 1629 self.attrs = attrs
1630 1630 self.headerpadchar = " "
1631 1631 self.headersepchar = "|"
1632 1632 self.datapadchar = " "
1633 1633 self.datasepchar = "|"
1634 1634
1635 1635 def display(self):
1636 1636 stream = genutils.Term.cout
1637 1637 allattrs = []
1638 1638 allattrset = set()
1639 1639 colwidths = {}
1640 1640 rows = []
1641 1641 for item in xiter(self.input, "default"):
1642 1642 row = {}
1643 1643 attrs = self.attrs
1644 1644 if not attrs:
1645 1645 attrs = xattrs(item, "default")
1646 1646 for attrname in attrs:
1647 1647 if attrname not in allattrset:
1648 1648 allattrs.append(attrname)
1649 1649 allattrset.add(attrname)
1650 1650 colwidths[attrname] = len(_attrname(attrname))
1651 1651 try:
1652 1652 value = _getattr(item, attrname, None)
1653 1653 except (KeyboardInterrupt, SystemExit):
1654 1654 raise
1655 1655 except Exception, exc:
1656 1656 value = exc
1657 1657 (align, width, text) = xformat(value, "cell", self.maxattrlength)
1658 1658 colwidths[attrname] = max(colwidths[attrname], width)
1659 1659 # remember alignment, length and colored parts
1660 1660 row[attrname] = (align, width, text)
1661 1661 rows.append(row)
1662 1662
1663 1663 stream.write("\n")
1664 1664 for (i, attrname) in enumerate(allattrs):
1665 1665 self.style_header(_attrname(attrname)).write(stream)
1666 1666 spc = colwidths[attrname] - len(_attrname(attrname))
1667 1667 if i < len(colwidths)-1:
1668 1668 stream.write(self.headerpadchar*spc)
1669 1669 stream.write(self.headersepchar)
1670 1670 stream.write("\n")
1671 1671
1672 1672 for row in rows:
1673 1673 for (i, attrname) in enumerate(allattrs):
1674 1674 (align, width, text) = row[attrname]
1675 1675 spc = colwidths[attrname] - width
1676 1676 if align == -1:
1677 1677 text.write(stream)
1678 1678 if i < len(colwidths)-1:
1679 1679 stream.write(self.datapadchar*spc)
1680 1680 elif align == 0:
1681 1681 spc = colwidths[attrname] - width
1682 1682 spc1 = spc//2
1683 1683 spc2 = spc-spc1
1684 1684 stream.write(self.datapadchar*spc1)
1685 1685 text.write(stream)
1686 1686 if i < len(colwidths)-1:
1687 1687 stream.write(self.datapadchar*spc2)
1688 1688 else:
1689 1689 stream.write(self.datapadchar*spc)
1690 1690 text.write(stream)
1691 1691 if i < len(colwidths)-1:
1692 1692 stream.write(self.datasepchar)
1693 1693 stream.write("\n")
1694 1694
1695 1695
1696 1696 class XMode(object):
1697 1697 """
1698 1698 An ``XMode`` object describes one enter mode available for an object
1699 1699 """
1700 1700 def __init__(self, object, mode, title=None, description=None):
1701 1701 """
1702 1702 Create a new ``XMode`` object for the object ``object``. This object
1703 1703 must support the enter mode ``mode`` (i.e. ``object.__xiter__(mode)``
1704 1704 must return an iterable). ``title`` and ``description`` will be
1705 1705 displayed in the browser when selecting among the available modes.
1706 1706 """
1707 1707 self.object = object
1708 1708 self.mode = mode
1709 1709 self.title = title
1710 1710 self.description = description
1711 1711
1712 1712 def __repr__(self):
1713 1713 return "<%s.%s object mode=%r at 0x%x>" % \
1714 1714 (self.__class__.__module__, self.__class__.__name__,
1715 1715 self.mode, id(self))
1716 1716
1717 1717 def __xrepr__(self, mode):
1718 1718 yield (-1, True)
1719 1719 if mode == "header" or mode == "footer":
1720 1720 yield (astyle.style_default, self.title)
1721 1721 else:
1722 1722 yield (astyle.style_default, repr(self))
1723 1723
1724 1724 def __xattrs__(self, mode):
1725 1725 if mode == "detail":
1726 1726 return ("object", "mode", "title", "description")
1727 1727 return ("title", "description")
1728 1728
1729 1729 def __xiter__(self, mode):
1730 1730 return xiter(self.object, self.mode)
1731 1731
1732 1732
1733 1733 class XAttr(object):
1734 1734 def __init__(self, object, name):
1735 1735 self.name = _attrname(name)
1736 1736
1737 1737 try:
1738 1738 self.value = _getattr(object, name)
1739 1739 except (KeyboardInterrupt, SystemExit):
1740 1740 raise
1741 1741 except Exception, exc:
1742 1742 if exc.__class__.__module__ == "exceptions":
1743 1743 self.value = exc.__class__.__name__
1744 1744 else:
1745 1745 self.value = "%s.%s" % \
1746 1746 (exc.__class__.__module__, exc.__class__.__name__)
1747 1747 self.type = self.value
1748 1748 else:
1749 1749 t = type(self.value)
1750 1750 if t.__module__ == "__builtin__":
1751 1751 self.type = t.__name__
1752 1752 else:
1753 1753 self.type = "%s.%s" % (t.__module__, t.__name__)
1754 1754
1755 1755 doc = None
1756 1756 if isinstance(name, basestring):
1757 1757 if name.endswith("()"):
1758 1758 doc = getattr(getattr(object, name[:-2]), "__doc__", None)
1759 1759 else:
1760 1760 try:
1761 1761 meta = getattr(type(object), name)
1762 1762 except AttributeError:
1763 1763 pass
1764 1764 else:
1765 1765 if isinstance(meta, property):
1766 1766 doc = getattr(meta, "__doc__", None)
1767 1767 elif callable(name):
1768 1768 doc = getattr(name, "__doc__", None)
1769 1769 if isinstance(doc, basestring):
1770 1770 doc = doc.strip()
1771 1771 self.doc = doc
1772 1772
1773 1773 def __xattrs__(self, mode):
1774 1774 return ("name", "type", "doc", "value")
1775 1775
1776 1776
1777 1777 try:
1778 1778 from ibrowse import ibrowse
1779 1779 except ImportError:
1780 1780 # No curses (probably Windows) => use ``idump`` as the default display.
1781 1781 defaultdisplay = idump
1782 1782 else:
1783 1783 defaultdisplay = ibrowse
1784 1784 __all__.append("ibrowse")
1785 1785
1786 1786
1787 1787 # If we're running under IPython, install an IPython displayhook that
1788 1788 # returns the object from Display.display(), else install a displayhook
1789 1789 # directly as sys.displayhook
1790 1790 try:
1791 1791 from IPython import ipapi
1792 1792 api = ipapi.get()
1793 1793 except (ImportError, AttributeError):
1794 1794 api = None
1795 1795
1796 1796 if api is not None:
1797 1797 def displayhook(self, obj):
1798 1798 if isinstance(obj, type) and issubclass(obj, Table):
1799 1799 obj = obj()
1800 1800 if isinstance(obj, Table):
1801 1801 obj = obj | defaultdisplay
1802 1802 if isinstance(obj, Display):
1803 1803 return obj.display()
1804 1804 else:
1805 1805 raise ipapi.TryNext
1806 1806 api.set_hook("result_display", displayhook)
1807 1807 else:
1808 1808 def installdisplayhook():
1809 1809 _originalhook = sys.displayhook
1810 1810 def displayhook(obj):
1811 1811 if isinstance(obj, type) and issubclass(obj, Table):
1812 1812 obj = obj()
1813 1813 if isinstance(obj, Table):
1814 1814 obj = obj | defaultdisplay
1815 1815 if isinstance(obj, Display):
1816 1816 return obj.display()
1817 1817 else:
1818 1818 _originalhook(obj)
1819 1819 sys.displayhook = displayhook
1820 1820 installdisplayhook()
General Comments 0
You need to be logged in to leave comments. Login now