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