##// END OF EJS Templates
Move ibrowse into a separate module.
walter.doerwald -
Show More
This diff has been collapsed as it changes many lines, (1413 lines changed) Show them Hide them
@@ -0,0 +1,1413 b''
1 # -*- coding: iso-8859-1 -*-
2
3 import curses
4
5 import astyle, ipipe
6
7
8 _ibrowse_help = """
9 down
10 Move the cursor to the next line.
11
12 up
13 Move the cursor to the previous line.
14
15 pagedown
16 Move the cursor down one page (minus overlap).
17
18 pageup
19 Move the cursor up one page (minus overlap).
20
21 left
22 Move the cursor left.
23
24 right
25 Move the cursor right.
26
27 home
28 Move the cursor to the first column.
29
30 end
31 Move the cursor to the last column.
32
33 prevattr
34 Move the cursor one attribute column to the left.
35
36 nextattr
37 Move the cursor one attribute column to the right.
38
39 pick
40 'Pick' the object under the cursor (i.e. the row the cursor is on). This
41 leaves the browser and returns the picked object to the caller. (In IPython
42 this object will be available as the '_' variable.)
43
44 pickattr
45 'Pick' the attribute under the cursor (i.e. the row/column the cursor is on).
46
47 pickallattrs
48 Pick' the complete column under the cursor (i.e. the attribute under the
49 cursor) from all currently fetched objects. These attributes will be returned
50 as a list.
51
52 tooglemark
53 Mark/unmark the object under the cursor. Marked objects have a '!' after the
54 row number).
55
56 pickmarked
57 'Pick' marked objects. Marked objects will be returned as a list.
58
59 pickmarkedattr
60 'Pick' the attribute under the cursor from all marked objects (This returns a
61 list).
62
63 enterdefault
64 Enter the object under the cursor. (what this mean depends on the object
65 itself (i.e. how it implements the '__xiter__' method). This opens a new
66 browser 'level'.
67
68 enter
69 Enter the object under the cursor. If the object provides different enter
70 modes a menu of all modes will be presented; choose one and enter it (via the
71 'enter' or 'enterdefault' command).
72
73 enterattr
74 Enter the attribute under the cursor.
75
76 leave
77 Leave the current browser level and go back to the previous one.
78
79 detail
80 Show a detail view of the object under the cursor. This shows the name, type,
81 doc string and value of the object attributes (and it might show more
82 attributes than in the list view, depending on the object).
83
84 detailattr
85 Show a detail view of the attribute under the cursor.
86
87 markrange
88 Mark all objects from the last marked object before the current cursor
89 position to the cursor position.
90
91 sortattrasc
92 Sort the objects (in ascending order) using the attribute under the cursor as
93 the sort key.
94
95 sortattrdesc
96 Sort the objects (in descending order) using the attribute under the cursor as
97 the sort key.
98
99 goto
100 Jump to a row. The row number can be entered at the bottom of the screen.
101
102 find
103 Search forward for a row. At the bottom of the screen the condition can be
104 entered.
105
106 findbackwards
107 Search backward for a row. At the bottom of the screen the condition can be
108 entered.
109
110 help
111 This screen.
112 """
113
114
115 class UnassignedKeyError(Exception):
116 """
117 Exception that is used for reporting unassigned keys.
118 """
119
120
121 class UnknownCommandError(Exception):
122 """
123 Exception that is used for reporting unknown command (this should never
124 happen).
125 """
126
127
128 class CommandError(Exception):
129 """
130 Exception that is used for reporting that a command can't be executed.
131 """
132
133
134 class _BrowserCachedItem(object):
135 # This is used internally by ``ibrowse`` to store a item together with its
136 # marked status.
137 __slots__ = ("item", "marked")
138
139 def __init__(self, item):
140 self.item = item
141 self.marked = False
142
143
144 class _BrowserHelp(object):
145 style_header = astyle.Style.fromstr("red:blacK")
146 # This is used internally by ``ibrowse`` for displaying the help screen.
147 def __init__(self, browser):
148 self.browser = browser
149
150 def __xrepr__(self, mode):
151 yield (-1, True)
152 if mode == "header" or mode == "footer":
153 yield (astyle.style_default, "ibrowse help screen")
154 else:
155 yield (astyle.style_default, repr(self))
156
157 def __xiter__(self, mode):
158 # Get reverse key mapping
159 allkeys = {}
160 for (key, cmd) in self.browser.keymap.iteritems():
161 allkeys.setdefault(cmd, []).append(key)
162
163 fields = ("key", "description")
164
165 for (i, command) in enumerate(_ibrowse_help.strip().split("\n\n")):
166 if i:
167 yield Fields(fields, key="", description="")
168
169 (name, description) = command.split("\n", 1)
170 keys = allkeys.get(name, [])
171 lines = textwrap.wrap(description, 60)
172
173 yield Fields(fields, description=astyle.Text((self.style_header, name)))
174 for i in xrange(max(len(keys), len(lines))):
175 try:
176 key = self.browser.keylabel(keys[i])
177 except IndexError:
178 key = ""
179 try:
180 line = lines[i]
181 except IndexError:
182 line = ""
183 yield Fields(fields, key=key, description=line)
184
185
186 class _BrowserLevel(object):
187 # This is used internally to store the state (iterator, fetch items,
188 # position of cursor and screen, etc.) of one browser level
189 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
190 # a stack.
191 def __init__(self, browser, input, iterator, mainsizey, *attrs):
192 self.browser = browser
193 self.input = input
194 self.header = [x for x in ipipe.xrepr(input, "header") if not isinstance(x[0], int)]
195 # iterator for the input
196 self.iterator = iterator
197
198 # is the iterator exhausted?
199 self.exhausted = False
200
201 # attributes to be display (autodetected if empty)
202 self.attrs = attrs
203
204 # fetched items (+ marked flag)
205 self.items = ipipe.deque()
206
207 # Number of marked objects
208 self.marked = 0
209
210 # Vertical cursor position
211 self.cury = 0
212
213 # Horizontal cursor position
214 self.curx = 0
215
216 # Index of first data column
217 self.datastartx = 0
218
219 # Index of first data line
220 self.datastarty = 0
221
222 # height of the data display area
223 self.mainsizey = mainsizey
224
225 # width of the data display area (changes when scrolling)
226 self.mainsizex = 0
227
228 # Size of row number (changes when scrolling)
229 self.numbersizex = 0
230
231 # Attribute names to display (in this order)
232 self.displayattrs = []
233
234 # index and name of attribute under the cursor
235 self.displayattr = (None, ipipe._default)
236
237 # Maps attribute names to column widths
238 self.colwidths = {}
239
240 self.fetch(mainsizey)
241 self.calcdisplayattrs()
242 # formatted attributes for the items on screen
243 # (i.e. self.items[self.datastarty:self.datastarty+self.mainsizey])
244 self.displayrows = [self.getrow(i) for i in xrange(len(self.items))]
245 self.calcwidths()
246 self.calcdisplayattr()
247
248 def fetch(self, count):
249 # Try to fill ``self.items`` with at least ``count`` objects.
250 have = len(self.items)
251 while not self.exhausted and have < count:
252 try:
253 item = self.iterator.next()
254 except StopIteration:
255 self.exhausted = True
256 break
257 else:
258 have += 1
259 self.items.append(_BrowserCachedItem(item))
260
261 def calcdisplayattrs(self):
262 # Calculate which attributes are available from the objects that are
263 # currently visible on screen (and store it in ``self.displayattrs``)
264 attrnames = set()
265 # If the browser object specifies a fixed list of attributes,
266 # simply use it.
267 if self.attrs:
268 self.displayattrs = self.attrs
269 else:
270 self.displayattrs = []
271 endy = min(self.datastarty+self.mainsizey, len(self.items))
272 for i in xrange(self.datastarty, endy):
273 for attrname in ipipe.xattrs(self.items[i].item, "default"):
274 if attrname not in attrnames:
275 self.displayattrs.append(attrname)
276 attrnames.add(attrname)
277
278 def getrow(self, i):
279 # Return a dictinary with the attributes for the object
280 # ``self.items[i]``. Attribute names are taken from
281 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
282 # called before.
283 row = {}
284 item = self.items[i].item
285 for attrname in self.displayattrs:
286 try:
287 value = ipipe._getattr(item, attrname, ipipe._default)
288 except (KeyboardInterrupt, SystemExit):
289 raise
290 except Exception, exc:
291 value = exc
292 # only store attribute if it exists (or we got an exception)
293 if value is not ipipe._default:
294 parts = []
295 totallength = 0
296 align = None
297 full = False
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 # remember alignment, length and colored parts
315 row[attrname] = (align, totallength, parts)
316 return row
317
318 def calcwidths(self):
319 # Recalculate the displayed fields and their width.
320 # ``calcdisplayattrs()'' must have been called and the cache
321 # for attributes of the objects on screen (``self.displayrows``)
322 # must have been filled. This returns a dictionary mapping
323 # colmn names to width.
324 self.colwidths = {}
325 for row in self.displayrows:
326 for attrname in self.displayattrs:
327 try:
328 length = row[attrname][1]
329 except KeyError:
330 length = 0
331 # always add attribute to colwidths, even if it doesn't exist
332 if attrname not in self.colwidths:
333 self.colwidths[attrname] = len(ipipe._attrname(attrname))
334 newwidth = max(self.colwidths[attrname], length)
335 self.colwidths[attrname] = newwidth
336
337 # How many characters do we need to paint the item number?
338 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
339 # How must space have we got to display data?
340 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
341 # width of all columns
342 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
343
344 def calcdisplayattr(self):
345 # Find out on which attribute the cursor is on and store this
346 # information in ``self.displayattr``.
347 pos = 0
348 for (i, attrname) in enumerate(self.displayattrs):
349 if pos+self.colwidths[attrname] >= self.curx:
350 self.displayattr = (i, attrname)
351 break
352 pos += self.colwidths[attrname]+1
353 else:
354 self.displayattr = (None, ipipe._default)
355
356 def moveto(self, x, y, refresh=False):
357 # Move the cursor to the position ``(x,y)`` (in data coordinates,
358 # not in screen coordinates). If ``refresh`` is true, all cached
359 # values will be recalculated (e.g. because the list has been
360 # resorted, so screen positions etc. are no longer valid).
361 olddatastarty = self.datastarty
362 oldx = self.curx
363 oldy = self.cury
364 x = int(x+0.5)
365 y = int(y+0.5)
366 newx = x # remember where we wanted to move
367 newy = y # remember where we wanted to move
368
369 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
370 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
371
372 # Make sure that the cursor didn't leave the main area vertically
373 if y < 0:
374 y = 0
375 self.fetch(y+scrollbordery+1) # try to get more items
376 if y >= len(self.items):
377 y = max(0, len(self.items)-1)
378
379 # Make sure that the cursor stays on screen vertically
380 if y < self.datastarty+scrollbordery:
381 self.datastarty = max(0, y-scrollbordery)
382 elif y >= self.datastarty+self.mainsizey-scrollbordery:
383 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
384 len(self.items)-self.mainsizey))
385
386 if refresh: # Do we need to refresh the complete display?
387 self.calcdisplayattrs()
388 endy = min(self.datastarty+self.mainsizey, len(self.items))
389 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
390 self.calcwidths()
391 # Did we scroll vertically => update displayrows
392 # and various other attributes
393 elif self.datastarty != olddatastarty:
394 # Recalculate which attributes we have to display
395 olddisplayattrs = self.displayattrs
396 self.calcdisplayattrs()
397 # If there are new attributes, recreate the cache
398 if self.displayattrs != olddisplayattrs:
399 endy = min(self.datastarty+self.mainsizey, len(self.items))
400 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
401 elif self.datastarty<olddatastarty: # we did scroll up
402 # drop rows from the end
403 del self.displayrows[self.datastarty-olddatastarty:]
404 # fetch new items
405 for i in xrange(olddatastarty-1,
406 self.datastarty-1, -1):
407 try:
408 row = self.getrow(i)
409 except IndexError:
410 # we didn't have enough objects to fill the screen
411 break
412 self.displayrows.insert(0, row)
413 else: # we did scroll down
414 # drop rows from the start
415 del self.displayrows[:self.datastarty-olddatastarty]
416 # fetch new items
417 for i in xrange(olddatastarty+self.mainsizey,
418 self.datastarty+self.mainsizey):
419 try:
420 row = self.getrow(i)
421 except IndexError:
422 # we didn't have enough objects to fill the screen
423 break
424 self.displayrows.append(row)
425 self.calcwidths()
426
427 # Make sure that the cursor didn't leave the data area horizontally
428 if x < 0:
429 x = 0
430 elif x >= self.datasizex:
431 x = max(0, self.datasizex-1)
432
433 # Make sure that the cursor stays on screen horizontally
434 if x < self.datastartx+scrollborderx:
435 self.datastartx = max(0, x-scrollborderx)
436 elif x >= self.datastartx+self.mainsizex-scrollborderx:
437 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
438 self.datasizex-self.mainsizex))
439
440 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
441 self.browser.beep()
442 else:
443 self.curx = x
444 self.cury = y
445 self.calcdisplayattr()
446
447 def sort(self, key, reverse=False):
448 """
449 Sort the currently list of items using the key function ``key``. If
450 ``reverse`` is true the sort order is reversed.
451 """
452 curitem = self.items[self.cury] # Remember where the cursor is now
453
454 # Sort items
455 def realkey(item):
456 return key(item.item)
457 self.items = ipipe.deque(sorted(self.items, key=realkey, reverse=reverse))
458
459 # Find out where the object under the cursor went
460 cury = self.cury
461 for (i, item) in enumerate(self.items):
462 if item is curitem:
463 cury = i
464 break
465
466 self.moveto(self.curx, cury, refresh=True)
467
468
469 class ibrowse(ipipe.Display):
470 # Show this many lines from the previous screen when paging horizontally
471 pageoverlapx = 1
472
473 # Show this many lines from the previous screen when paging vertically
474 pageoverlapy = 1
475
476 # Start scrolling when the cursor is less than this number of columns
477 # away from the left or right screen edge
478 scrollborderx = 10
479
480 # Start scrolling when the cursor is less than this number of lines
481 # away from the top or bottom screen edge
482 scrollbordery = 5
483
484 # Accelerate by this factor when scrolling horizontally
485 acceleratex = 1.05
486
487 # Accelerate by this factor when scrolling vertically
488 acceleratey = 1.05
489
490 # The maximum horizontal scroll speed
491 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
492 maxspeedx = 0.5
493
494 # The maximum vertical scroll speed
495 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
496 maxspeedy = 0.5
497
498 # The maximum number of header lines for browser level
499 # if the nesting is deeper, only the innermost levels are displayed
500 maxheaders = 5
501
502 # The approximate maximum length of a column entry
503 maxattrlength = 200
504
505 # Styles for various parts of the GUI
506 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
507 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
508 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
509 style_colheader = astyle.Style.fromstr("blue:white:reverse")
510 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
511 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
512 style_number = astyle.Style.fromstr("blue:white:reverse")
513 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
514 style_sep = astyle.Style.fromstr("blue:black")
515 style_data = astyle.Style.fromstr("white:black")
516 style_datapad = astyle.Style.fromstr("blue:black:bold")
517 style_footer = astyle.Style.fromstr("black:white")
518 style_report = astyle.Style.fromstr("white:black")
519
520 # Column separator in header
521 headersepchar = "|"
522
523 # Character for padding data cell entries
524 datapadchar = "."
525
526 # Column separator in data area
527 datasepchar = "|"
528
529 # Character to use for "empty" cell (i.e. for non-existing attributes)
530 nodatachar = "-"
531
532 # Prompts for modes that require keyboard input
533 prompts = {
534 "goto": "goto object #: ",
535 "find": "find expression: ",
536 "findbackwards": "find backwards expression: "
537 }
538
539 # Maps curses key codes to "function" names
540 keymap = {
541 ord("q"): "quit",
542 curses.KEY_UP: "up",
543 curses.KEY_DOWN: "down",
544 curses.KEY_PPAGE: "pageup",
545 curses.KEY_NPAGE: "pagedown",
546 curses.KEY_LEFT: "left",
547 curses.KEY_RIGHT: "right",
548 curses.KEY_HOME: "home",
549 curses.KEY_END: "end",
550 ord("<"): "prevattr",
551 0x1b: "prevattr", # SHIFT-TAB
552 ord(">"): "nextattr",
553 ord("\t"):"nextattr", # TAB
554 ord("p"): "pick",
555 ord("P"): "pickattr",
556 ord("C"): "pickallattrs",
557 ord("m"): "pickmarked",
558 ord("M"): "pickmarkedattr",
559 ord("\n"): "enterdefault",
560 # FIXME: What's happening here?
561 8: "leave",
562 127: "leave",
563 curses.KEY_BACKSPACE: "leave",
564 ord("x"): "leave",
565 ord("h"): "help",
566 ord("e"): "enter",
567 ord("E"): "enterattr",
568 ord("d"): "detail",
569 ord("D"): "detailattr",
570 ord(" "): "tooglemark",
571 ord("r"): "markrange",
572 ord("v"): "sortattrasc",
573 ord("V"): "sortattrdesc",
574 ord("g"): "goto",
575 ord("f"): "find",
576 ord("b"): "findbackwards",
577 }
578
579 def __init__(self, *attrs):
580 """
581 Create a new browser. If ``attrs`` is not empty, it is the list
582 of attributes that will be displayed in the browser, otherwise
583 these will be determined by the objects on screen.
584 """
585 self.attrs = attrs
586
587 # Stack of browser levels
588 self.levels = []
589 # how many colums to scroll (Changes when accelerating)
590 self.stepx = 1.
591
592 # how many rows to scroll (Changes when accelerating)
593 self.stepy = 1.
594
595 # Beep on the edges of the data area? (Will be set to ``False``
596 # once the cursor hits the edge of the screen, so we don't get
597 # multiple beeps).
598 self._dobeep = True
599
600 # Cache for registered ``curses`` colors and styles.
601 self._styles = {}
602 self._colors = {}
603 self._maxcolor = 1
604
605 # How many header lines do we want to paint (the numbers of levels
606 # we have, but with an upper bound)
607 self._headerlines = 1
608
609 # Index of first header line
610 self._firstheaderline = 0
611
612 # curses window
613 self.scr = None
614 # report in the footer line (error, executed command etc.)
615 self._report = None
616
617 # value to be returned to the caller (set by commands)
618 self.returnvalue = None
619
620 # The mode the browser is in
621 # e.g. normal browsing or entering an argument for a command
622 self.mode = "default"
623
624 # The partially entered row number for the goto command
625 self.goto = ""
626
627 def nextstepx(self, step):
628 """
629 Accelerate horizontally.
630 """
631 return max(1., min(step*self.acceleratex,
632 self.maxspeedx*self.levels[-1].mainsizex))
633
634 def nextstepy(self, step):
635 """
636 Accelerate vertically.
637 """
638 return max(1., min(step*self.acceleratey,
639 self.maxspeedy*self.levels[-1].mainsizey))
640
641 def getstyle(self, style):
642 """
643 Register the ``style`` with ``curses`` or get it from the cache,
644 if it has been registered before.
645 """
646 try:
647 return self._styles[style.fg, style.bg, style.attrs]
648 except KeyError:
649 attrs = 0
650 for b in astyle.A2CURSES:
651 if style.attrs & b:
652 attrs |= astyle.A2CURSES[b]
653 try:
654 color = self._colors[style.fg, style.bg]
655 except KeyError:
656 curses.init_pair(
657 self._maxcolor,
658 astyle.COLOR2CURSES[style.fg],
659 astyle.COLOR2CURSES[style.bg]
660 )
661 color = curses.color_pair(self._maxcolor)
662 self._colors[style.fg, style.bg] = color
663 self._maxcolor += 1
664 c = color | attrs
665 self._styles[style.fg, style.bg, style.attrs] = c
666 return c
667
668 def addstr(self, y, x, begx, endx, text, style):
669 """
670 A version of ``curses.addstr()`` that can handle ``x`` coordinates
671 that are outside the screen.
672 """
673 text2 = text[max(0, begx-x):max(0, endx-x)]
674 if text2:
675 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
676 return len(text)
677
678 def addchr(self, y, x, begx, endx, c, l, style):
679 x0 = max(x, begx)
680 x1 = min(x+l, endx)
681 if x1>x0:
682 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
683 return l
684
685 def _calcheaderlines(self, levels):
686 # Calculate how many headerlines do we have to display, if we have
687 # ``levels`` browser levels
688 if levels is None:
689 levels = len(self.levels)
690 self._headerlines = min(self.maxheaders, levels)
691 self._firstheaderline = levels-self._headerlines
692
693 def getstylehere(self, style):
694 """
695 Return a style for displaying the original style ``style``
696 in the row the cursor is on.
697 """
698 return astyle.Style(style.fg, style.bg, style.attrs | astyle.A_BOLD)
699
700 def report(self, msg):
701 """
702 Store the message ``msg`` for display below the footer line. This
703 will be displayed as soon as the screen is redrawn.
704 """
705 self._report = msg
706
707 def enter(self, item, mode, *attrs):
708 """
709 Enter the object ``item`` in the mode ``mode``. If ``attrs`` is
710 specified, it will be used as a fixed list of attributes to display.
711 """
712 try:
713 iterator = ipipe.xiter(item, mode)
714 except (KeyboardInterrupt, SystemExit):
715 raise
716 except Exception, exc:
717 curses.beep()
718 self.report(exc)
719 else:
720 self._calcheaderlines(len(self.levels)+1)
721 level = _BrowserLevel(
722 self,
723 item,
724 iterator,
725 self.scrsizey-1-self._headerlines-2,
726 *attrs
727 )
728 self.levels.append(level)
729
730 def startkeyboardinput(self, mode):
731 """
732 Enter mode ``mode``, which requires keyboard input.
733 """
734 self.mode = mode
735 self.keyboardinput = ""
736 self.cursorpos = 0
737
738 def executekeyboardinput(self, mode):
739 exe = getattr(self, "exe_%s" % mode, None)
740 if exe is not None:
741 exe()
742 self.mode = "default"
743
744 def keylabel(self, keycode):
745 """
746 Return a pretty name for the ``curses`` key ``keycode`` (used in the
747 help screen and in reports about unassigned keys).
748 """
749 if keycode <= 0xff:
750 specialsnames = {
751 ord("\n"): "RETURN",
752 ord(" "): "SPACE",
753 ord("\t"): "TAB",
754 ord("\x7f"): "DELETE",
755 ord("\x08"): "BACKSPACE",
756 }
757 if keycode in specialsnames:
758 return specialsnames[keycode]
759 return repr(chr(keycode))
760 for name in dir(curses):
761 if name.startswith("KEY_") and getattr(curses, name) == keycode:
762 return name
763 return str(keycode)
764
765 def beep(self, force=False):
766 if force or self._dobeep:
767 curses.beep()
768 # don't beep again (as long as the same key is pressed)
769 self._dobeep = False
770
771 def cmd_quit(self):
772 self.returnvalue = None
773 return True
774
775 def cmd_up(self):
776 level = self.levels[-1]
777 self.report("up")
778 level.moveto(level.curx, level.cury-self.stepy)
779
780 def cmd_down(self):
781 level = self.levels[-1]
782 self.report("down")
783 level.moveto(level.curx, level.cury+self.stepy)
784
785 def cmd_pageup(self):
786 level = self.levels[-1]
787 self.report("page up")
788 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
789
790 def cmd_pagedown(self):
791 level = self.levels[-1]
792 self.report("page down")
793 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
794
795 def cmd_left(self):
796 level = self.levels[-1]
797 self.report("left")
798 level.moveto(level.curx-self.stepx, level.cury)
799
800 def cmd_right(self):
801 level = self.levels[-1]
802 self.report("right")
803 level.moveto(level.curx+self.stepx, level.cury)
804
805 def cmd_home(self):
806 level = self.levels[-1]
807 self.report("home")
808 level.moveto(0, level.cury)
809
810 def cmd_end(self):
811 level = self.levels[-1]
812 self.report("end")
813 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
814
815 def cmd_prevattr(self):
816 level = self.levels[-1]
817 if level.displayattr[0] is None or level.displayattr[0] == 0:
818 self.beep()
819 else:
820 self.report("prevattr")
821 pos = 0
822 for (i, attrname) in enumerate(level.displayattrs):
823 if i == level.displayattr[0]-1:
824 break
825 pos += level.colwidths[attrname] + 1
826 level.moveto(pos, level.cury)
827
828 def cmd_nextattr(self):
829 level = self.levels[-1]
830 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
831 self.beep()
832 else:
833 self.report("nextattr")
834 pos = 0
835 for (i, attrname) in enumerate(level.displayattrs):
836 if i == level.displayattr[0]+1:
837 break
838 pos += level.colwidths[attrname] + 1
839 level.moveto(pos, level.cury)
840
841 def cmd_pick(self):
842 level = self.levels[-1]
843 self.returnvalue = level.items[level.cury].item
844 return True
845
846 def cmd_pickattr(self):
847 level = self.levels[-1]
848 attrname = level.displayattr[1]
849 if attrname is ipipe._default:
850 curses.beep()
851 self.report(AttributeError(ipipe._attrname(attrname)))
852 return
853 attr = ipipe._getattr(level.items[level.cury].item, attrname)
854 if attr is ipipe._default:
855 curses.beep()
856 self.report(AttributeError(ipipe._attrname(attrname)))
857 else:
858 self.returnvalue = attr
859 return True
860
861 def cmd_pickallattrs(self):
862 level = self.levels[-1]
863 attrname = level.displayattr[1]
864 if attrname is ipipe._default:
865 curses.beep()
866 self.report(AttributeError(ipipe._attrname(attrname)))
867 return
868 result = []
869 for cache in level.items:
870 attr = ipipe._getattr(cache.item, attrname)
871 if attr is not ipipe._default:
872 result.append(attr)
873 self.returnvalue = result
874 return True
875
876 def cmd_pickmarked(self):
877 level = self.levels[-1]
878 self.returnvalue = [cache.item for cache in level.items if cache.marked]
879 return True
880
881 def cmd_pickmarkedattr(self):
882 level = self.levels[-1]
883 attrname = level.displayattr[1]
884 if attrname is ipipe._default:
885 curses.beep()
886 self.report(AttributeError(ipipe._attrname(attrname)))
887 return
888 result = []
889 for cache in level.items:
890 if cache.marked:
891 attr = ipipe._getattr(cache.item, attrname)
892 if attr is not ipipe._default:
893 result.append(attr)
894 self.returnvalue = result
895 return True
896
897 def cmd_markrange(self):
898 level = self.levels[-1]
899 self.report("markrange")
900 start = None
901 if level.items:
902 for i in xrange(level.cury, -1, -1):
903 if level.items[i].marked:
904 start = i
905 break
906 if start is None:
907 self.report(CommandError("no mark before cursor"))
908 curses.beep()
909 else:
910 for i in xrange(start, level.cury+1):
911 cache = level.items[i]
912 if not cache.marked:
913 cache.marked = True
914 level.marked += 1
915
916 def cmd_enterdefault(self):
917 level = self.levels[-1]
918 try:
919 item = level.items[level.cury].item
920 except IndexError:
921 self.report(CommandError("No object"))
922 curses.beep()
923 else:
924 self.report("entering object (default mode)...")
925 self.enter(item, "default")
926
927 def cmd_leave(self):
928 self.report("leave")
929 if len(self.levels) > 1:
930 self._calcheaderlines(len(self.levels)-1)
931 self.levels.pop(-1)
932 else:
933 self.report(CommandError("This is the last level"))
934 curses.beep()
935
936 def cmd_enter(self):
937 level = self.levels[-1]
938 try:
939 item = level.items[level.cury].item
940 except IndexError:
941 self.report(CommandError("No object"))
942 curses.beep()
943 else:
944 self.report("entering object...")
945 self.enter(item, None)
946
947 def cmd_enterattr(self):
948 level = self.levels[-1]
949 attrname = level.displayattr[1]
950 if attrname is ipipe._default:
951 curses.beep()
952 self.report(AttributeError(ipipe._attrname(attrname)))
953 return
954 try:
955 item = level.items[level.cury].item
956 except IndexError:
957 self.report(CommandError("No object"))
958 curses.beep()
959 else:
960 attr = ipipe._getattr(item, attrname)
961 if attr is ipipe._default:
962 self.report(AttributeError(ipipe._attrname(attrname)))
963 else:
964 self.report("entering object attribute %s..." % ipipe._attrname(attrname))
965 self.enter(attr, None)
966
967 def cmd_detail(self):
968 level = self.levels[-1]
969 try:
970 item = level.items[level.cury].item
971 except IndexError:
972 self.report(CommandError("No object"))
973 curses.beep()
974 else:
975 self.report("entering detail view for object...")
976 self.enter(item, "detail")
977
978 def cmd_detailattr(self):
979 level = self.levels[-1]
980 attrname = level.displayattr[1]
981 if attrname is ipipe._default:
982 curses.beep()
983 self.report(AttributeError(ipipe._attrname(attrname)))
984 return
985 try:
986 item = level.items[level.cury].item
987 except IndexError:
988 self.report(CommandError("No object"))
989 curses.beep()
990 else:
991 attr = ipipe._getattr(item, attrname)
992 if attr is ipipe._default:
993 self.report(AttributeError(ipipe._attrname(attrname)))
994 else:
995 self.report("entering detail view for attribute...")
996 self.enter(attr, "detail")
997
998 def cmd_tooglemark(self):
999 level = self.levels[-1]
1000 self.report("toggle mark")
1001 try:
1002 item = level.items[level.cury]
1003 except IndexError: # no items?
1004 pass
1005 else:
1006 if item.marked:
1007 item.marked = False
1008 level.marked -= 1
1009 else:
1010 item.marked = True
1011 level.marked += 1
1012
1013 def cmd_sortattrasc(self):
1014 level = self.levels[-1]
1015 attrname = level.displayattr[1]
1016 if attrname is ipipe._default:
1017 curses.beep()
1018 self.report(AttributeError(ipipe._attrname(attrname)))
1019 return
1020 self.report("sort by %s (ascending)" % ipipe._attrname(attrname))
1021 def key(item):
1022 try:
1023 return ipipe._getattr(item, attrname, None)
1024 except (KeyboardInterrupt, SystemExit):
1025 raise
1026 except Exception:
1027 return None
1028 level.sort(key)
1029
1030 def cmd_sortattrdesc(self):
1031 level = self.levels[-1]
1032 attrname = level.displayattr[1]
1033 if attrname is ipipe._default:
1034 curses.beep()
1035 self.report(AttributeError(ipipe._attrname(attrname)))
1036 return
1037 self.report("sort by %s (descending)" % ipipe._attrname(attrname))
1038 def key(item):
1039 try:
1040 return ipipe._getattr(item, attrname, None)
1041 except (KeyboardInterrupt, SystemExit):
1042 raise
1043 except Exception:
1044 return None
1045 level.sort(key, reverse=True)
1046
1047 def cmd_goto(self):
1048 self.startkeyboardinput("goto")
1049
1050 def exe_goto(self):
1051 level = self.levels[-1]
1052 if self.keyboardinput:
1053 level.moveto(level.curx, int(self.keyboardinput))
1054
1055 def cmd_find(self):
1056 self.startkeyboardinput("find")
1057
1058 def exe_find(self):
1059 level = self.levels[-1]
1060 if self.keyboardinput:
1061 while True:
1062 cury = level.cury
1063 level.moveto(level.curx, cury+1)
1064 if cury == level.cury:
1065 curses.beep()
1066 break
1067 item = level.items[level.cury].item
1068 try:
1069 if eval(self.keyboardinput, globals(), ipipe.AttrNamespace(item)):
1070 break
1071 except (KeyboardInterrupt, SystemExit):
1072 raise
1073 except Exception, exc:
1074 self.report(exc)
1075 curses.beep()
1076 break # break on error
1077
1078 def cmd_findbackwards(self):
1079 self.startkeyboardinput("findbackwards")
1080
1081 def exe_findbackwards(self):
1082 level = self.levels[-1]
1083 if self.keyboardinput:
1084 while level.cury:
1085 level.moveto(level.curx, level.cury-1)
1086 item = level.items[level.cury].item
1087 try:
1088 if eval(self.keyboardinput, globals(), ipipe.AttrNamespace(item)):
1089 break
1090 except (KeyboardInterrupt, SystemExit):
1091 raise
1092 except Exception, exc:
1093 self.report(exc)
1094 curses.beep()
1095 break # break on error
1096 else:
1097 curses.beep()
1098
1099 def cmd_help(self):
1100 """
1101 The help command
1102 """
1103 for level in self.levels:
1104 if isinstance(level.input, _BrowserHelp):
1105 curses.beep()
1106 self.report(CommandError("help already active"))
1107 return
1108
1109 self.enter(_BrowserHelp(self), "default")
1110
1111 def _dodisplay(self, scr):
1112 """
1113 This method is the workhorse of the browser. It handles screen
1114 drawing and the keyboard.
1115 """
1116 self.scr = scr
1117 curses.halfdelay(1)
1118 footery = 2
1119
1120 keys = []
1121 for (key, cmd) in self.keymap.iteritems():
1122 if cmd == "quit":
1123 keys.append("%s=%s" % (self.keylabel(key), cmd))
1124 for (key, cmd) in self.keymap.iteritems():
1125 if cmd == "help":
1126 keys.append("%s=%s" % (self.keylabel(key), cmd))
1127 helpmsg = " | %s" % " ".join(keys)
1128
1129 scr.clear()
1130 msg = "Fetching first batch of objects..."
1131 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1132 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1133 scr.refresh()
1134
1135 lastc = -1
1136
1137 self.levels = []
1138 # enter the first level
1139 self.enter(self.input, ipipe.xiter(self.input, "default"), *self.attrs)
1140
1141 self._calcheaderlines(None)
1142
1143 while True:
1144 level = self.levels[-1]
1145 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1146 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1147
1148 # Paint object header
1149 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1150 lv = self.levels[i]
1151 posx = 0
1152 posy = i-self._firstheaderline
1153 endx = self.scrsizex
1154 if i: # not the first level
1155 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1156 if not self.levels[i-1].exhausted:
1157 msg += "+"
1158 msg += ") "
1159 endx -= len(msg)+1
1160 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1161 for (style, text) in lv.header:
1162 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1163 if posx >= endx:
1164 break
1165 if i:
1166 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1167 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1168
1169 if not level.items:
1170 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1171 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1172 scr.clrtobot()
1173 else:
1174 # Paint column headers
1175 scr.move(self._headerlines, 0)
1176 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1177 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1178 begx = level.numbersizex+3
1179 posx = begx-level.datastartx
1180 for attrname in level.displayattrs:
1181 strattrname = ipipe._attrname(attrname)
1182 cwidth = level.colwidths[attrname]
1183 header = strattrname.ljust(cwidth)
1184 if attrname == level.displayattr[1]:
1185 style = self.style_colheaderhere
1186 else:
1187 style = self.style_colheader
1188 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1189 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1190 if posx >= self.scrsizex:
1191 break
1192 else:
1193 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1194
1195 # Paint rows
1196 posy = self._headerlines+1+level.datastarty
1197 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1198 cache = level.items[i]
1199 if i == level.cury:
1200 style = self.style_numberhere
1201 else:
1202 style = self.style_number
1203
1204 posy = self._headerlines+1+i-level.datastarty
1205 posx = begx-level.datastartx
1206
1207 scr.move(posy, 0)
1208 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1209 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1210
1211 for attrname in level.displayattrs:
1212 cwidth = level.colwidths[attrname]
1213 try:
1214 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1215 except KeyError:
1216 align = 2
1217 style = astyle.style_nodata
1218 padstyle = self.style_datapad
1219 sepstyle = self.style_sep
1220 if i == level.cury:
1221 padstyle = self.getstylehere(padstyle)
1222 sepstyle = self.getstylehere(sepstyle)
1223 if align == 2:
1224 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1225 else:
1226 if align == 1:
1227 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1228 elif align == 0:
1229 pad1 = (cwidth-length)//2
1230 pad2 = cwidth-length-len(pad1)
1231 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1232 for (style, text) in parts:
1233 if i == level.cury:
1234 style = self.getstylehere(style)
1235 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1236 if posx >= self.scrsizex:
1237 break
1238 if align == -1:
1239 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1240 elif align == 0:
1241 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1242 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1243 else:
1244 scr.clrtoeol()
1245
1246 # Add blank row headers for the rest of the screen
1247 for posy in xrange(posy+1, self.scrsizey-2):
1248 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1249 scr.clrtoeol()
1250
1251 posy = self.scrsizey-footery
1252 # Display footer
1253 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1254
1255 if level.exhausted:
1256 flag = ""
1257 else:
1258 flag = "+"
1259
1260 endx = self.scrsizex-len(helpmsg)-1
1261 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1262
1263 posx = 0
1264 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1265 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1266 try:
1267 item = level.items[level.cury].item
1268 except IndexError: # empty
1269 pass
1270 else:
1271 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1272 if not isinstance(nostyle, int):
1273 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1274 if posx >= endx:
1275 break
1276
1277 attrstyle = [(astyle.style_default, "no attribute")]
1278 attrname = level.displayattr[1]
1279 if attrname is not ipipe._default and attrname is not None:
1280 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1281 posx += self.addstr(posy, posx, 0, endx, ipipe._attrname(attrname), self.style_footer)
1282 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1283 try:
1284 attr = ipipe._getattr(item, attrname)
1285 except (SystemExit, KeyboardInterrupt):
1286 raise
1287 except Exception, exc:
1288 attr = exc
1289 if attr is not ipipe._default:
1290 attrstyle = ipipe.xrepr(attr, "footer")
1291 for (nostyle, text) in attrstyle:
1292 if not isinstance(nostyle, int):
1293 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1294 if posx >= endx:
1295 break
1296
1297 try:
1298 # Display input prompt
1299 if self.mode in self.prompts:
1300 scr.addstr(self.scrsizey-1, 0,
1301 self.prompts[self.mode] + self.keyboardinput,
1302 self.getstyle(astyle.style_default))
1303 # Display report
1304 else:
1305 if self._report is not None:
1306 if isinstance(self._report, Exception):
1307 style = self.getstyle(astyle.style_error)
1308 if self._report.__class__.__module__ == "exceptions":
1309 msg = "%s: %s" % \
1310 (self._report.__class__.__name__, self._report)
1311 else:
1312 msg = "%s.%s: %s" % \
1313 (self._report.__class__.__module__,
1314 self._report.__class__.__name__, self._report)
1315 else:
1316 style = self.getstyle(self.style_report)
1317 msg = self._report
1318 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1319 self._report = None
1320 else:
1321 scr.move(self.scrsizey-1, 0)
1322 except curses.error:
1323 # Protect against error from writing to the last line
1324 pass
1325 scr.clrtoeol()
1326
1327 # Position cursor
1328 if self.mode in self.prompts:
1329 scr.move(self.scrsizey-1, len(self.prompts[self.mode])+self.cursorpos)
1330 else:
1331 scr.move(
1332 1+self._headerlines+level.cury-level.datastarty,
1333 level.numbersizex+3+level.curx-level.datastartx
1334 )
1335 scr.refresh()
1336
1337 # Check keyboard
1338 while True:
1339 c = scr.getch()
1340 if self.mode in self.prompts:
1341 if c in (8, 127, curses.KEY_BACKSPACE):
1342 if self.cursorpos:
1343 self.keyboardinput = self.keyboardinput[:self.cursorpos-1] + self.keyboardinput[self.cursorpos:]
1344 self.cursorpos -= 1
1345 break
1346 else:
1347 curses.beep()
1348 elif c == curses.KEY_LEFT:
1349 if self.cursorpos:
1350 self.cursorpos -= 1
1351 break
1352 else:
1353 curses.beep()
1354 elif c == curses.KEY_RIGHT:
1355 if self.cursorpos < len(self.keyboardinput):
1356 self.cursorpos += 1
1357 break
1358 else:
1359 curses.beep()
1360 elif c in (curses.KEY_UP, curses.KEY_DOWN): # cancel
1361 self.mode = "default"
1362 break
1363 elif c == ord("\n"):
1364 self.executekeyboardinput(self.mode)
1365 break
1366 elif c != -1:
1367 try:
1368 c = chr(c)
1369 except ValueError:
1370 curses.beep()
1371 else:
1372 if (self.mode == "goto" and not "0" <= c <= "9"):
1373 curses.beep()
1374 else:
1375 self.keyboardinput = self.keyboardinput[:self.cursorpos] + c + self.keyboardinput[self.cursorpos:]
1376 self.cursorpos += 1
1377 break # Redisplay
1378 else:
1379 # if no key is pressed slow down and beep again
1380 if c == -1:
1381 self.stepx = 1.
1382 self.stepy = 1.
1383 self._dobeep = True
1384 else:
1385 # if a different key was pressed slow down and beep too
1386 if c != lastc:
1387 lastc = c
1388 self.stepx = 1.
1389 self.stepy = 1.
1390 self._dobeep = True
1391 cmdname = self.keymap.get(c, None)
1392 if cmdname is None:
1393 self.report(
1394 UnassignedKeyError("Unassigned key %s" %
1395 self.keylabel(c)))
1396 else:
1397 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1398 if cmdfunc is None:
1399 self.report(
1400 UnknownCommandError("Unknown command %r" %
1401 (cmdname,)))
1402 elif cmdfunc():
1403 returnvalue = self.returnvalue
1404 self.returnvalue = None
1405 return returnvalue
1406 self.stepx = self.nextstepx(self.stepx)
1407 self.stepy = self.nextstepy(self.stepy)
1408 curses.flushinp() # get rid of type ahead
1409 break # Redisplay
1410 self.scr = None
1411
1412 def display(self):
1413 return curses.wrapper(self._dodisplay)
This diff has been collapsed as it changes many lines, (1436 lines changed) Show them Hide them
@@ -1,3230 +1,1820 b''
1 # -*- coding: iso-8859-1 -*-
1 # -*- coding: iso-8859-1 -*-
2
2
3 """
3 """
4 ``ipipe`` provides classes to be used in an interactive Python session. Doing a
4 ``ipipe`` provides classes to be used in an interactive Python session. Doing a
5 ``from ipipe import *`` is the preferred way to do this. The name of all
5 ``from ipipe import *`` is the preferred way to do this. The name of all
6 objects imported this way starts with ``i`` to minimize collisions.
6 objects imported this way starts with ``i`` to minimize collisions.
7
7
8 ``ipipe`` supports "pipeline expressions", which is something resembling Unix
8 ``ipipe`` supports "pipeline expressions", which is something resembling Unix
9 pipes. An example is:
9 pipes. An example is:
10
10
11 >>> ienv | isort("key.lower()")
11 >>> ienv | isort("key.lower()")
12
12
13 This gives a listing of all environment variables sorted by name.
13 This gives a listing of all environment variables sorted by name.
14
14
15
15
16 There are three types of objects in a pipeline expression:
16 There are three types of objects in a pipeline expression:
17
17
18 * ``Table``s: These objects produce items. Examples are ``ls`` (listing the
18 * ``Table``s: These objects produce items. Examples are ``ls`` (listing the
19 current directory, ``ienv`` (listing environment variables), ``ipwd`` (listing
19 current directory, ``ienv`` (listing environment variables), ``ipwd`` (listing
20 user account) and ``igrp`` (listing user groups). A ``Table`` must be the
20 user account) and ``igrp`` (listing user groups). A ``Table`` must be the
21 first object in a pipe expression.
21 first object in a pipe expression.
22
22
23 * ``Pipe``s: These objects sit in the middle of a pipe expression. They
23 * ``Pipe``s: These objects sit in the middle of a pipe expression. They
24 transform the input in some way (e.g. filtering or sorting it). Examples are:
24 transform the input in some way (e.g. filtering or sorting it). Examples are:
25 ``ifilter`` (which filters the input pipe), ``isort`` (which sorts the input
25 ``ifilter`` (which filters the input pipe), ``isort`` (which sorts the input
26 pipe) and ``ieval`` (which evaluates a function or expression for each object
26 pipe) and ``ieval`` (which evaluates a function or expression for each object
27 in the input pipe).
27 in the input pipe).
28
28
29 * ``Display``s: These objects can be put as the last object in a pipeline
29 * ``Display``s: These objects can be put as the last object in a pipeline
30 expression. There are responsible for displaying the result of the pipeline
30 expression. There are responsible for displaying the result of the pipeline
31 expression. If a pipeline expression doesn't end in a display object a default
31 expression. If a pipeline expression doesn't end in a display object a default
32 display objects will be used. One example is ``browse`` which is a ``curses``
32 display objects will be used. One example is ``browse`` which is a ``curses``
33 based browser.
33 based browser.
34
34
35
35
36 Adding support for pipeline expressions to your own objects can be done through
36 Adding support for pipeline expressions to your own objects can be done through
37 three extensions points (all of them optional):
37 three extensions points (all of them optional):
38
38
39 * An object that will be displayed as a row by a ``Display`` object should
39 * An object that will be displayed as a row by a ``Display`` object should
40 implement the method ``__xattrs__(self, mode)``. This method must return a
40 implement the method ``__xattrs__(self, mode)``. This method must return a
41 sequence of attribute names. This sequence may also contain integers, which
41 sequence of attribute names. This sequence may also contain integers, which
42 will be treated as sequence indizes. Also supported is ``None``, which uses
42 will be treated as sequence indizes. Also supported is ``None``, which uses
43 the object itself and callables which will be called with the object as the
43 the object itself and callables which will be called with the object as the
44 an argument. If ``__xattrs__()`` isn't implemented ``(None,)`` will be used as
44 an argument. If ``__xattrs__()`` isn't implemented ``(None,)`` will be used as
45 the attribute sequence (i.e. the object itself (it's ``repr()`` format) will
45 the attribute sequence (i.e. the object itself (it's ``repr()`` format) will
46 be being displayed. The global function ``xattrs()`` implements this
46 be being displayed. The global function ``xattrs()`` implements this
47 functionality.
47 functionality.
48
48
49 * When an object ``foo`` is displayed in the header, footer or table cell of the
49 * When an object ``foo`` is displayed in the header, footer or table cell of the
50 browser ``foo.__xrepr__(mode)`` is called. Mode can be ``"header"`` or
50 browser ``foo.__xrepr__(mode)`` is called. Mode can be ``"header"`` or
51 ``"footer"`` for the header or footer line and ``"cell"`` for a table cell.
51 ``"footer"`` for the header or footer line and ``"cell"`` for a table cell.
52 ``__xrepr__()```must return an iterable (e.g. by being a generator) which
52 ``__xrepr__()```must return an iterable (e.g. by being a generator) which
53 produces the following items: The first item should be a tuple containing
53 produces the following items: The first item should be a tuple containing
54 the alignment (-1 left aligned, 0 centered and 1 right aligned) and whether
54 the alignment (-1 left aligned, 0 centered and 1 right aligned) and whether
55 the complete output must be displayed or if the browser is allowed to stop
55 the complete output must be displayed or if the browser is allowed to stop
56 output after enough text has been produced (e.g. a syntax highlighted text
56 output after enough text has been produced (e.g. a syntax highlighted text
57 line would use ``True``, but for a large data structure (i.e. a nested list,
57 line would use ``True``, but for a large data structure (i.e. a nested list,
58 tuple or dictionary) ``False`` would be used). The other output ``__xrepr__()``
58 tuple or dictionary) ``False`` would be used). The other output ``__xrepr__()``
59 may produce is tuples of ``Style```objects and text (which contain the text
59 may produce is tuples of ``Style```objects and text (which contain the text
60 representation of the object; see the ``astyle`` module). If ``__xrepr__()``
60 representation of the object; see the ``astyle`` module). If ``__xrepr__()``
61 recursively outputs a data structure the function ``xrepr(object, mode)`` can
61 recursively outputs a data structure the function ``xrepr(object, mode)`` can
62 be used and ``"default"`` must be passed as the mode in these calls. This in
62 be used and ``"default"`` must be passed as the mode in these calls. This in
63 turn calls the ``__xrepr__()`` method on ``object`` (or uses ``repr(object)``
63 turn calls the ``__xrepr__()`` method on ``object`` (or uses ``repr(object)``
64 as the string representation if ``__xrepr__()`` doesn't exist).
64 as the string representation if ``__xrepr__()`` doesn't exist).
65
65
66 * Objects that can be iterated by ``Pipe``s must implement the method
66 * Objects that can be iterated by ``Pipe``s must implement the method
67 ``__xiter__(self, mode)``. ``mode`` can take the following values:
67 ``__xiter__(self, mode)``. ``mode`` can take the following values:
68
68
69 - ``"default"``: This is the default value and ist always used by pipeline
69 - ``"default"``: This is the default value and ist always used by pipeline
70 expressions. Other values are only used in the browser.
70 expressions. Other values are only used in the browser.
71 - ``None``: This value is passed by the browser. The object must return an
71 - ``None``: This value is passed by the browser. The object must return an
72 iterable of ``XMode`` objects describing all modes supported by the object.
72 iterable of ``XMode`` objects describing all modes supported by the object.
73 (This should never include ``"default"`` or ``None``).
73 (This should never include ``"default"`` or ``None``).
74 - Any other value that the object supports.
74 - Any other value that the object supports.
75
75
76 The global function ``xiter()`` can be called to get such an iterator. If
76 The global function ``xiter()`` can be called to get such an iterator. If
77 the method ``_xiter__`` isn't implemented, ``xiter()`` falls back to
77 the method ``_xiter__`` isn't implemented, ``xiter()`` falls back to
78 ``__iter__``. In addition to that, dictionaries and modules receive special
78 ``__iter__``. In addition to that, dictionaries and modules receive special
79 treatment (returning an iterator over ``(key, value)`` pairs). This makes it
79 treatment (returning an iterator over ``(key, value)`` pairs). This makes it
80 possible to use dictionaries and modules in pipeline expressions, for example:
80 possible to use dictionaries and modules in pipeline expressions, for example:
81
81
82 >>> import sys
82 >>> import sys
83 >>> sys | ifilter("isinstance(value, int)") | idump
83 >>> sys | ifilter("isinstance(value, int)") | idump
84 key |value
84 key |value
85 api_version| 1012
85 api_version| 1012
86 dllhandle | 503316480
86 dllhandle | 503316480
87 hexversion | 33817328
87 hexversion | 33817328
88 maxint |2147483647
88 maxint |2147483647
89 maxunicode | 65535
89 maxunicode | 65535
90 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
90 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
91 ...
91 ...
92
92
93 Note: The expression strings passed to ``ifilter()`` and ``isort()`` can
93 Note: The expression strings passed to ``ifilter()`` and ``isort()`` can
94 refer to the object to be filtered or sorted via the variable ``_`` and to any
94 refer to the object to be filtered or sorted via the variable ``_`` and to any
95 of the attributes of the object, i.e.:
95 of the attributes of the object, i.e.:
96
96
97 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
97 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
98
98
99 does the same as
99 does the same as
100
100
101 >>> sys.modules | ifilter("value is not None") | isort("key.lower()")
101 >>> sys.modules | ifilter("value is not None") | isort("key.lower()")
102
102
103 In addition to expression strings, it's possible to pass callables (taking
103 In addition to expression strings, it's possible to pass callables (taking
104 the object as an argument) to ``ifilter()``, ``isort()`` and ``ieval()``:
104 the object as an argument) to ``ifilter()``, ``isort()`` and ``ieval()``:
105
105
106 >>> sys | ifilter(lambda _:isinstance(_.value, int)) \
106 >>> sys | ifilter(lambda _:isinstance(_.value, int)) \
107 ... | ieval(lambda _: (_.key, hex(_.value))) | idump
107 ... | ieval(lambda _: (_.key, hex(_.value))) | idump
108 0 |1
108 0 |1
109 api_version|0x3f4
109 api_version|0x3f4
110 dllhandle |0x1e000000
110 dllhandle |0x1e000000
111 hexversion |0x20402f0
111 hexversion |0x20402f0
112 maxint |0x7fffffff
112 maxint |0x7fffffff
113 maxunicode |0xffff
113 maxunicode |0xffff
114 """
114 """
115
115
116 import sys, os, os.path, stat, glob, new, csv, datetime, types
116 import sys, os, os.path, stat, glob, new, csv, datetime, types
117 import textwrap, itertools, mimetypes
117 import textwrap, itertools, mimetypes
118
118
119 try: # Python 2.3 compatibility
119 try: # Python 2.3 compatibility
120 import collections
120 import collections
121 except ImportError:
121 except ImportError:
122 deque = list
122 deque = list
123 else:
123 else:
124 deque = collections.deque
124 deque = collections.deque
125
125
126 try: # Python 2.3 compatibility
126 try: # Python 2.3 compatibility
127 set
127 set
128 except NameError:
128 except NameError:
129 import sets
129 import sets
130 set = sets.Set
130 set = sets.Set
131
131
132 try: # Python 2.3 compatibility
132 try: # Python 2.3 compatibility
133 sorted
133 sorted
134 except NameError:
134 except NameError:
135 def sorted(iterator, key=None, reverse=False):
135 def sorted(iterator, key=None, reverse=False):
136 items = list(iterator)
136 items = list(iterator)
137 if key is not None:
137 if key is not None:
138 items.sort(lambda i1, i2: cmp(key(i1), key(i2)))
138 items.sort(lambda i1, i2: cmp(key(i1), key(i2)))
139 else:
139 else:
140 items.sort()
140 items.sort()
141 if reverse:
141 if reverse:
142 items.reverse()
142 items.reverse()
143 return items
143 return items
144
144
145 try:
145 try:
146 import pwd
146 import pwd
147 except ImportError:
147 except ImportError:
148 pwd = None
148 pwd = None
149
149
150 try:
150 try:
151 import grp
151 import grp
152 except ImportError:
152 except ImportError:
153 grp = None
153 grp = None
154
154
155 try:
156 import curses
157 except ImportError:
158 curses = None
159
160 import path
155 import path
161 try:
156 try:
162 from IPython import genutils
157 from IPython import genutils
163 except ImportError:
158 except ImportError:
164 pass
159 pass
165
160
166 import astyle
161 import astyle
167
162
168
163
169 __all__ = [
164 __all__ = [
170 "ifile", "ils", "iglob", "iwalk", "ipwdentry", "ipwd", "igrpentry", "igrp",
165 "ifile", "ils", "iglob", "iwalk", "ipwdentry", "ipwd", "igrpentry", "igrp",
171 "icsv", "ix", "ichain", "isort", "ifilter", "ieval", "ienum", "ienv",
166 "icsv", "ix", "ichain", "isort", "ifilter", "ieval", "ienum", "ienv",
172 "idump", "iless"
167 "idump", "iless"
173 ]
168 ]
174
169
175
170
176 os.stat_float_times(True) # enable microseconds
171 os.stat_float_times(True) # enable microseconds
177
172
178
173
179 class _AttrNamespace(object):
174 class AttrNamespace(object):
180 """
175 """
181 Internal helper class that is used for providing a namespace for evaluating
176 Helper class that is used for providing a namespace for evaluating
182 expressions containg attribute names of an object.
177 expressions containing attribute names of an object.
183 """
178 """
184 def __init__(self, wrapped):
179 def __init__(self, wrapped):
185 self.wrapped = wrapped
180 self.wrapped = wrapped
186
181
187 def __getitem__(self, name):
182 def __getitem__(self, name):
188 if name == "_":
183 if name == "_":
189 return self.wrapped
184 return self.wrapped
190 try:
185 try:
191 return getattr(self.wrapped, name)
186 return getattr(self.wrapped, name)
192 except AttributeError:
187 except AttributeError:
193 raise KeyError(name)
188 raise KeyError(name)
194
189
195 # Python 2.3 compatibility
190 # Python 2.3 compatibility
196 # use eval workaround to find out which names are used in the
191 # use eval workaround to find out which names are used in the
197 # eval string and put them into the locals. This works for most
192 # eval string and put them into the locals. This works for most
198 # normal uses case, bizarre ones like accessing the locals()
193 # normal uses case, bizarre ones like accessing the locals()
199 # will fail
194 # will fail
200 try:
195 try:
201 eval("_", None, _AttrNamespace(None))
196 eval("_", None, AttrNamespace(None))
202 except TypeError:
197 except TypeError:
203 real_eval = eval
198 real_eval = eval
204 def eval(codestring, _globals, _locals):
199 def eval(codestring, _globals, _locals):
205 """
200 """
206 eval(source[, globals[, locals]]) -> value
201 eval(source[, globals[, locals]]) -> value
207
202
208 Evaluate the source in the context of globals and locals.
203 Evaluate the source in the context of globals and locals.
209 The source may be a string representing a Python expression
204 The source may be a string representing a Python expression
210 or a code object as returned by compile().
205 or a code object as returned by compile().
211 The globals must be a dictionary and locals can be any mappping.
206 The globals must be a dictionary and locals can be any mappping.
212
207
213 This function is a workaround for the shortcomings of
208 This function is a workaround for the shortcomings of
214 Python 2.3's eval.
209 Python 2.3's eval.
215 """
210 """
216
211
217 code = compile(codestring, "_eval", "eval")
212 code = compile(codestring, "_eval", "eval")
218 newlocals = {}
213 newlocals = {}
219 for name in code.co_names:
214 for name in code.co_names:
220 try:
215 try:
221 newlocals[name] = _locals[name]
216 newlocals[name] = _locals[name]
222 except KeyError:
217 except KeyError:
223 pass
218 pass
224 return real_eval(code, _globals, newlocals)
219 return real_eval(code, _globals, newlocals)
225
220
226
221
227 _default = object()
222 _default = object()
228
223
229 def item(iterator, index, default=_default):
224 def item(iterator, index, default=_default):
230 """
225 """
231 Return the ``index``th item from the iterator ``iterator``.
226 Return the ``index``th item from the iterator ``iterator``.
232 ``index`` must be an integer (negative integers are relative to the
227 ``index`` must be an integer (negative integers are relative to the
233 end (i.e. the last item produced by the iterator)).
228 end (i.e. the last item produced by the iterator)).
234
229
235 If ``default`` is given, this will be the default value when
230 If ``default`` is given, this will be the default value when
236 the iterator doesn't contain an item at this position. Otherwise an
231 the iterator doesn't contain an item at this position. Otherwise an
237 ``IndexError`` will be raised.
232 ``IndexError`` will be raised.
238
233
239 Note that using this function will partially or totally exhaust the
234 Note that using this function will partially or totally exhaust the
240 iterator.
235 iterator.
241 """
236 """
242 i = index
237 i = index
243 if i>=0:
238 if i>=0:
244 for item in iterator:
239 for item in iterator:
245 if not i:
240 if not i:
246 return item
241 return item
247 i -= 1
242 i -= 1
248 else:
243 else:
249 i = -index
244 i = -index
250 cache = deque()
245 cache = deque()
251 for item in iterator:
246 for item in iterator:
252 cache.append(item)
247 cache.append(item)
253 if len(cache)>i:
248 if len(cache)>i:
254 cache.popleft()
249 cache.popleft()
255 if len(cache)==i:
250 if len(cache)==i:
256 return cache.popleft()
251 return cache.popleft()
257 if default is _default:
252 if default is _default:
258 raise IndexError(index)
253 raise IndexError(index)
259 else:
254 else:
260 return default
255 return default
261
256
262
257
263 class Table(object):
258 class Table(object):
264 """
259 """
265 A ``Table`` is an object that produces items (just like a normal Python
260 A ``Table`` is an object that produces items (just like a normal Python
266 iterator/generator does) and can be used as the first object in a pipeline
261 iterator/generator does) and can be used as the first object in a pipeline
267 expression. The displayhook will open the default browser for such an object
262 expression. The displayhook will open the default browser for such an object
268 (instead of simply printing the ``repr()`` result).
263 (instead of simply printing the ``repr()`` result).
269 """
264 """
270
265
271 # We want to support ``foo`` and ``foo()`` in pipeline expression:
266 # We want to support ``foo`` and ``foo()`` in pipeline expression:
272 # So we implement the required operators (``|`` and ``+``) in the metaclass,
267 # So we implement the required operators (``|`` and ``+``) in the metaclass,
273 # instantiate the class and forward the operator to the instance
268 # instantiate the class and forward the operator to the instance
274 class __metaclass__(type):
269 class __metaclass__(type):
275 def __iter__(self):
270 def __iter__(self):
276 return iter(self())
271 return iter(self())
277
272
278 def __or__(self, other):
273 def __or__(self, other):
279 return self() | other
274 return self() | other
280
275
281 def __add__(self, other):
276 def __add__(self, other):
282 return self() + other
277 return self() + other
283
278
284 def __radd__(self, other):
279 def __radd__(self, other):
285 return other + self()
280 return other + self()
286
281
287 def __getitem__(self, index):
282 def __getitem__(self, index):
288 return self()[index]
283 return self()[index]
289
284
290 def __getitem__(self, index):
285 def __getitem__(self, index):
291 return item(self, index)
286 return item(self, index)
292
287
293 def __contains__(self, item):
288 def __contains__(self, item):
294 for haveitem in self:
289 for haveitem in self:
295 if item == haveitem:
290 if item == haveitem:
296 return True
291 return True
297 return False
292 return False
298
293
299 def __or__(self, other):
294 def __or__(self, other):
300 # autoinstantiate right hand side
295 # autoinstantiate right hand side
301 if isinstance(other, type) and issubclass(other, (Table, Display)):
296 if isinstance(other, type) and issubclass(other, (Table, Display)):
302 other = other()
297 other = other()
303 # treat simple strings and functions as ``ieval`` instances
298 # treat simple strings and functions as ``ieval`` instances
304 elif not isinstance(other, Display) and not isinstance(other, Table):
299 elif not isinstance(other, Display) and not isinstance(other, Table):
305 other = ieval(other)
300 other = ieval(other)
306 # forward operations to the right hand side
301 # forward operations to the right hand side
307 return other.__ror__(self)
302 return other.__ror__(self)
308
303
309 def __add__(self, other):
304 def __add__(self, other):
310 # autoinstantiate right hand side
305 # autoinstantiate right hand side
311 if isinstance(other, type) and issubclass(other, Table):
306 if isinstance(other, type) and issubclass(other, Table):
312 other = other()
307 other = other()
313 return ichain(self, other)
308 return ichain(self, other)
314
309
315 def __radd__(self, other):
310 def __radd__(self, other):
316 # autoinstantiate left hand side
311 # autoinstantiate left hand side
317 if isinstance(other, type) and issubclass(other, Table):
312 if isinstance(other, type) and issubclass(other, Table):
318 other = other()
313 other = other()
319 return ichain(other, self)
314 return ichain(other, self)
320
315
321 def __iter__(self):
316 def __iter__(self):
322 return xiter(self, "default")
317 return xiter(self, "default")
323
318
324
319
325 class Pipe(Table):
320 class Pipe(Table):
326 """
321 """
327 A ``Pipe`` is an object that can be used in a pipeline expression. It
322 A ``Pipe`` is an object that can be used in a pipeline expression. It
328 processes the objects it gets from its input ``Table``/``Pipe``. Note that
323 processes the objects it gets from its input ``Table``/``Pipe``. Note that
329 a ``Pipe`` object can't be used as the first object in a pipeline
324 a ``Pipe`` object can't be used as the first object in a pipeline
330 expression, as it doesn't produces items itself.
325 expression, as it doesn't produces items itself.
331 """
326 """
332 class __metaclass__(Table.__metaclass__):
327 class __metaclass__(Table.__metaclass__):
333 def __ror__(self, input):
328 def __ror__(self, input):
334 return input | self()
329 return input | self()
335
330
336 def __ror__(self, input):
331 def __ror__(self, input):
337 # autoinstantiate left hand side
332 # autoinstantiate left hand side
338 if isinstance(input, type) and issubclass(input, Table):
333 if isinstance(input, type) and issubclass(input, Table):
339 input = input()
334 input = input()
340 self.input = input
335 self.input = input
341 return self
336 return self
342
337
343
338
344 def _getattr(obj, name, default=_default):
339 def _getattr(obj, name, default=_default):
345 """
340 """
346 Internal helper for getting an attribute of an item. If ``name`` is ``None``
341 Internal helper for getting an attribute of an item. If ``name`` is ``None``
347 return the object itself. If ``name`` is an integer, use ``__getitem__``
342 return the object itself. If ``name`` is an integer, use ``__getitem__``
348 instead. If the attribute or item does not exist, return ``default``.
343 instead. If the attribute or item does not exist, return ``default``.
349 """
344 """
350 if name is None:
345 if name is None:
351 return obj
346 return obj
352 elif isinstance(name, basestring):
347 elif isinstance(name, basestring):
353 if name.endswith("()"):
348 if name.endswith("()"):
354 return getattr(obj, name[:-2], default)()
349 return getattr(obj, name[:-2], default)()
355 else:
350 else:
356 return getattr(obj, name, default)
351 return getattr(obj, name, default)
357 elif callable(name):
352 elif callable(name):
358 try:
353 try:
359 return name(obj)
354 return name(obj)
360 except AttributeError:
355 except AttributeError:
361 return default
356 return default
362 else:
357 else:
363 try:
358 try:
364 return obj[name]
359 return obj[name]
365 except IndexError:
360 except IndexError:
366 return default
361 return default
367
362
368
363
369 def _attrname(name):
364 def _attrname(name):
370 """
365 """
371 Internal helper that gives a proper name for the attribute ``name``
366 Internal helper that gives a proper name for the attribute ``name``
372 (which might be ``None`` or an ``int``).
367 (which might be ``None`` or an ``int``).
373 """
368 """
374 if name is None:
369 if name is None:
375 return "_"
370 return "_"
376 elif isinstance(name, basestring):
371 elif isinstance(name, basestring):
377 return name
372 return name
378 elif callable(name):
373 elif callable(name):
379 return getattr(name, "__xname__", name.__name__)
374 return getattr(name, "__xname__", name.__name__)
380 else:
375 else:
381 return str(name)
376 return str(name)
382
377
383
378
384 def xrepr(item, mode):
379 def xrepr(item, mode):
385 try:
380 try:
386 func = item.__xrepr__
381 func = item.__xrepr__
387 except AttributeError:
382 except AttributeError:
388 pass
383 pass
389 else:
384 else:
390 try:
385 try:
391 for x in func(mode):
386 for x in func(mode):
392 yield x
387 yield x
393 except (KeyboardInterrupt, SystemExit):
388 except (KeyboardInterrupt, SystemExit):
394 raise
389 raise
395 except Exception:
390 except Exception:
396 yield (-1, True)
391 yield (-1, True)
397 yield (astyle.style_default, repr(item))
392 yield (astyle.style_default, repr(item))
398 return
393 return
399 if item is None:
394 if item is None:
400 yield (-1, True)
395 yield (-1, True)
401 yield (astyle.style_type_none, repr(item))
396 yield (astyle.style_type_none, repr(item))
402 elif isinstance(item, bool):
397 elif isinstance(item, bool):
403 yield (-1, True)
398 yield (-1, True)
404 yield (astyle.style_type_bool, repr(item))
399 yield (astyle.style_type_bool, repr(item))
405 elif isinstance(item, str):
400 elif isinstance(item, str):
406 yield (-1, True)
401 yield (-1, True)
407 if mode == "cell":
402 if mode == "cell":
408 yield (astyle.style_default, repr(item.expandtabs(tab))[1:-1])
403 yield (astyle.style_default, repr(item.expandtabs(tab))[1:-1])
409 else:
404 else:
410 yield (astyle.style_default, repr(item))
405 yield (astyle.style_default, repr(item))
411 elif isinstance(item, unicode):
406 elif isinstance(item, unicode):
412 yield (-1, True)
407 yield (-1, True)
413 if mode == "cell":
408 if mode == "cell":
414 yield (astyle.style_default, repr(item.expandtabs(tab))[2:-1])
409 yield (astyle.style_default, repr(item.expandtabs(tab))[2:-1])
415 else:
410 else:
416 yield (astyle.style_default, repr(item))
411 yield (astyle.style_default, repr(item))
417 elif isinstance(item, (int, long, float)):
412 elif isinstance(item, (int, long, float)):
418 yield (1, True)
413 yield (1, True)
419 yield (astyle.style_type_number, repr(item))
414 yield (astyle.style_type_number, repr(item))
420 elif isinstance(item, complex):
415 elif isinstance(item, complex):
421 yield (-1, True)
416 yield (-1, True)
422 yield (astyle.style_type_number, repr(item))
417 yield (astyle.style_type_number, repr(item))
423 elif isinstance(item, datetime.datetime):
418 elif isinstance(item, datetime.datetime):
424 yield (-1, True)
419 yield (-1, True)
425 if mode == "cell":
420 if mode == "cell":
426 # Don't use strftime() here, as this requires year >= 1900
421 # Don't use strftime() here, as this requires year >= 1900
427 yield (astyle.style_type_datetime,
422 yield (astyle.style_type_datetime,
428 "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
423 "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
429 (item.year, item.month, item.day,
424 (item.year, item.month, item.day,
430 item.hour, item.minute, item.second,
425 item.hour, item.minute, item.second,
431 item.microsecond),
426 item.microsecond),
432 )
427 )
433 else:
428 else:
434 yield (astyle.style_type_datetime, repr(item))
429 yield (astyle.style_type_datetime, repr(item))
435 elif isinstance(item, datetime.date):
430 elif isinstance(item, datetime.date):
436 yield (-1, True)
431 yield (-1, True)
437 if mode == "cell":
432 if mode == "cell":
438 yield (astyle.style_type_datetime,
433 yield (astyle.style_type_datetime,
439 "%04d-%02d-%02d" % (item.year, item.month, item.day))
434 "%04d-%02d-%02d" % (item.year, item.month, item.day))
440 else:
435 else:
441 yield (astyle.style_type_datetime, repr(item))
436 yield (astyle.style_type_datetime, repr(item))
442 elif isinstance(item, datetime.time):
437 elif isinstance(item, datetime.time):
443 yield (-1, True)
438 yield (-1, True)
444 if mode == "cell":
439 if mode == "cell":
445 yield (astyle.style_type_datetime,
440 yield (astyle.style_type_datetime,
446 "%02d:%02d:%02d.%06d" % \
441 "%02d:%02d:%02d.%06d" % \
447 (item.hour, item.minute, item.second, item.microsecond))
442 (item.hour, item.minute, item.second, item.microsecond))
448 else:
443 else:
449 yield (astyle.style_type_datetime, repr(item))
444 yield (astyle.style_type_datetime, repr(item))
450 elif isinstance(item, datetime.timedelta):
445 elif isinstance(item, datetime.timedelta):
451 yield (-1, True)
446 yield (-1, True)
452 yield (astyle.style_type_datetime, repr(item))
447 yield (astyle.style_type_datetime, repr(item))
453 elif isinstance(item, Exception):
448 elif isinstance(item, Exception):
454 yield (-1, True)
449 yield (-1, True)
455 if item.__class__.__module__ == "exceptions":
450 if item.__class__.__module__ == "exceptions":
456 classname = item.__class__.__name__
451 classname = item.__class__.__name__
457 else:
452 else:
458 classname = "%s.%s" % \
453 classname = "%s.%s" % \
459 (item.__class__.__module__, item.__class__.__name__)
454 (item.__class__.__module__, item.__class__.__name__)
460 if mode == "header" or mode == "footer":
455 if mode == "header" or mode == "footer":
461 yield (astyle.style_error, "%s: %s" % (classname, item))
456 yield (astyle.style_error, "%s: %s" % (classname, item))
462 else:
457 else:
463 yield (astyle.style_error, classname)
458 yield (astyle.style_error, classname)
464 elif isinstance(item, (list, tuple)):
459 elif isinstance(item, (list, tuple)):
465 yield (-1, False)
460 yield (-1, False)
466 if mode == "header" or mode == "footer":
461 if mode == "header" or mode == "footer":
467 if item.__class__.__module__ == "__builtin__":
462 if item.__class__.__module__ == "__builtin__":
468 classname = item.__class__.__name__
463 classname = item.__class__.__name__
469 else:
464 else:
470 classname = "%s.%s" % \
465 classname = "%s.%s" % \
471 (item.__class__.__module__,item.__class__.__name__)
466 (item.__class__.__module__,item.__class__.__name__)
472 yield (astyle.style_default,
467 yield (astyle.style_default,
473 "<%s object with %d items at 0x%x>" % \
468 "<%s object with %d items at 0x%x>" % \
474 (classname, len(item), id(item)))
469 (classname, len(item), id(item)))
475 else:
470 else:
476 if isinstance(item, list):
471 if isinstance(item, list):
477 yield (astyle.style_default, "[")
472 yield (astyle.style_default, "[")
478 end = "]"
473 end = "]"
479 else:
474 else:
480 yield (astyle.style_default, "(")
475 yield (astyle.style_default, "(")
481 end = ")"
476 end = ")"
482 for (i, subitem) in enumerate(item):
477 for (i, subitem) in enumerate(item):
483 if i:
478 if i:
484 yield (astyle.style_default, ", ")
479 yield (astyle.style_default, ", ")
485 for part in xrepr(subitem, "default"):
480 for part in xrepr(subitem, "default"):
486 yield part
481 yield part
487 yield (astyle.style_default, end)
482 yield (astyle.style_default, end)
488 elif isinstance(item, (dict, types.DictProxyType)):
483 elif isinstance(item, (dict, types.DictProxyType)):
489 yield (-1, False)
484 yield (-1, False)
490 if mode == "header" or mode == "footer":
485 if mode == "header" or mode == "footer":
491 if item.__class__.__module__ == "__builtin__":
486 if item.__class__.__module__ == "__builtin__":
492 classname = item.__class__.__name__
487 classname = item.__class__.__name__
493 else:
488 else:
494 classname = "%s.%s" % \
489 classname = "%s.%s" % \
495 (item.__class__.__module__,item.__class__.__name__)
490 (item.__class__.__module__,item.__class__.__name__)
496 yield (astyle.style_default,
491 yield (astyle.style_default,
497 "<%s object with %d items at 0x%x>" % \
492 "<%s object with %d items at 0x%x>" % \
498 (classname, len(item), id(item)))
493 (classname, len(item), id(item)))
499 else:
494 else:
500 if isinstance(item, dict):
495 if isinstance(item, dict):
501 yield (astyle.style_default, "{")
496 yield (astyle.style_default, "{")
502 end = "}"
497 end = "}"
503 else:
498 else:
504 yield (astyle.style_default, "dictproxy((")
499 yield (astyle.style_default, "dictproxy((")
505 end = "})"
500 end = "})"
506 for (i, (key, value)) in enumerate(item.iteritems()):
501 for (i, (key, value)) in enumerate(item.iteritems()):
507 if i:
502 if i:
508 yield (astyle.style_default, ", ")
503 yield (astyle.style_default, ", ")
509 for part in xrepr(key, "default"):
504 for part in xrepr(key, "default"):
510 yield part
505 yield part
511 yield (astyle.style_default, ": ")
506 yield (astyle.style_default, ": ")
512 for part in xrepr(value, "default"):
507 for part in xrepr(value, "default"):
513 yield part
508 yield part
514 yield (astyle.style_default, end)
509 yield (astyle.style_default, end)
515 else:
510 else:
516 yield (-1, True)
511 yield (-1, True)
517 yield (astyle.style_default, repr(item))
512 yield (astyle.style_default, repr(item))
518
513
519
514
520 def xattrs(item, mode):
515 def xattrs(item, mode):
521 try:
516 try:
522 func = item.__xattrs__
517 func = item.__xattrs__
523 except AttributeError:
518 except AttributeError:
524 if mode == "detail":
519 if mode == "detail":
525 return dir(item)
520 return dir(item)
526 else:
521 else:
527 return (None,)
522 return (None,)
528 else:
523 else:
529 try:
524 try:
530 return func(mode)
525 return func(mode)
531 except (KeyboardInterrupt, SystemExit):
526 except (KeyboardInterrupt, SystemExit):
532 raise
527 raise
533 except Exception:
528 except Exception:
534 return (None,)
529 return (None,)
535
530
536
531
537 def xiter(item, mode):
532 def xiter(item, mode):
538 if mode == "detail":
533 if mode == "detail":
539 def items():
534 def items():
540 for name in xattrs(item, mode):
535 for name in xattrs(item, mode):
541 yield XAttr(item, name)
536 yield XAttr(item, name)
542 return items()
537 return items()
543 try:
538 try:
544 func = item.__xiter__
539 func = item.__xiter__
545 except AttributeError:
540 except AttributeError:
546 if isinstance(item, (dict, types.DictProxyType)):
541 if isinstance(item, (dict, types.DictProxyType)):
547 def items(item):
542 def items(item):
548 fields = ("key", "value")
543 fields = ("key", "value")
549 for (key, value) in item.iteritems():
544 for (key, value) in item.iteritems():
550 yield Fields(fields, key=key, value=value)
545 yield Fields(fields, key=key, value=value)
551 return items(item)
546 return items(item)
552 elif isinstance(item, new.module):
547 elif isinstance(item, new.module):
553 def items(item):
548 def items(item):
554 fields = ("key", "value")
549 fields = ("key", "value")
555 for key in sorted(item.__dict__):
550 for key in sorted(item.__dict__):
556 yield Fields(fields, key=key, value=getattr(item, key))
551 yield Fields(fields, key=key, value=getattr(item, key))
557 return items(item)
552 return items(item)
558 elif isinstance(item, basestring):
553 elif isinstance(item, basestring):
559 if not len(item):
554 if not len(item):
560 raise ValueError("can't enter empty string")
555 raise ValueError("can't enter empty string")
561 lines = item.splitlines()
556 lines = item.splitlines()
562 if len(lines) <= 1:
557 if len(lines) <= 1:
563 raise ValueError("can't enter one line string")
558 raise ValueError("can't enter one line string")
564 return iter(lines)
559 return iter(lines)
565 return iter(item)
560 return iter(item)
566 else:
561 else:
567 return iter(func(mode)) # iter() just to be safe
562 return iter(func(mode)) # iter() just to be safe
568
563
569
564
570 class ichain(Pipe):
565 class ichain(Pipe):
571 """
566 """
572 Chains multiple ``Table``s into one.
567 Chains multiple ``Table``s into one.
573 """
568 """
574
569
575 def __init__(self, *iters):
570 def __init__(self, *iters):
576 self.iters = iters
571 self.iters = iters
577
572
578 def __xiter__(self, mode):
573 def __xiter__(self, mode):
579 return itertools.chain(*self.iters)
574 return itertools.chain(*self.iters)
580
575
581 def __xrepr__(self, mode):
576 def __xrepr__(self, mode):
582 yield (-1, True)
577 yield (-1, True)
583 if mode == "header" or mode == "footer":
578 if mode == "header" or mode == "footer":
584 for (i, item) in enumerate(self.iters):
579 for (i, item) in enumerate(self.iters):
585 if i:
580 if i:
586 yield (astyle.style_default, "+")
581 yield (astyle.style_default, "+")
587 if isinstance(item, Pipe):
582 if isinstance(item, Pipe):
588 yield (astyle.style_default, "(")
583 yield (astyle.style_default, "(")
589 for part in xrepr(item, mode):
584 for part in xrepr(item, mode):
590 yield part
585 yield part
591 if isinstance(item, Pipe):
586 if isinstance(item, Pipe):
592 yield (astyle.style_default, ")")
587 yield (astyle.style_default, ")")
593 else:
588 else:
594 yield (astyle.style_default, repr(self))
589 yield (astyle.style_default, repr(self))
595
590
596 def __repr__(self):
591 def __repr__(self):
597 args = ", ".join([repr(it) for it in self.iters])
592 args = ", ".join([repr(it) for it in self.iters])
598 return "%s.%s(%s)" % \
593 return "%s.%s(%s)" % \
599 (self.__class__.__module__, self.__class__.__name__, args)
594 (self.__class__.__module__, self.__class__.__name__, args)
600
595
601
596
602 class ifile(path.path):
597 class ifile(path.path):
603 """
598 """
604 file (or directory) object.
599 file (or directory) object.
605 """
600 """
606
601
607 def __add_(self, other):
602 def __add_(self, other):
608 return ifile(path._base(self) + other)
603 return ifile(path._base(self) + other)
609
604
610 def __radd_(self, other):
605 def __radd_(self, other):
611 return ifile(other + path._base(self))
606 return ifile(other + path._base(self))
612
607
613 def __div_(self, other):
608 def __div_(self, other):
614 return ifile(path.__div__(self, other))
609 return ifile(path.__div__(self, other))
615
610
616 def getcwd():
611 def getcwd():
617 """ Return the current working directory as a path object. """
612 """ Return the current working directory as a path object. """
618 return ifile(path.path.getcwd())
613 return ifile(path.path.getcwd())
619 getcwd = staticmethod(getcwd)
614 getcwd = staticmethod(getcwd)
620
615
621 def abspath(self):
616 def abspath(self):
622 return ifile(path.path.abspath(self))
617 return ifile(path.path.abspath(self))
623
618
624 def normcase(self):
619 def normcase(self):
625 return ifile(path.path.normcase(self))
620 return ifile(path.path.normcase(self))
626
621
627 def normpath(self):
622 def normpath(self):
628 return ifile(path.path.normpath(self))
623 return ifile(path.path.normpath(self))
629
624
630 def realpath(self):
625 def realpath(self):
631 return ifile(path.path.realpath(self))
626 return ifile(path.path.realpath(self))
632
627
633 def expanduser(self):
628 def expanduser(self):
634 return ifile(path.path.expanduser(self))
629 return ifile(path.path.expanduser(self))
635
630
636 def expandvars(self):
631 def expandvars(self):
637 return ifile(path.path.expandvars(self))
632 return ifile(path.path.expandvars(self))
638
633
639 def dirname(self):
634 def dirname(self):
640 return ifile(path.path.dirname(self))
635 return ifile(path.path.dirname(self))
641
636
642 parent = property(dirname, None, None, path.path.parent.__doc__)
637 parent = property(dirname, None, None, path.path.parent.__doc__)
643
638
644 def splitpath(self):
639 def splitpath(self):
645 (parent, child) = path.path.splitpath(self)
640 (parent, child) = path.path.splitpath(self)
646 return (ifile(parent), child)
641 return (ifile(parent), child)
647
642
648 def splitdrive(self):
643 def splitdrive(self):
649 (drive, rel) = path.path.splitdrive(self)
644 (drive, rel) = path.path.splitdrive(self)
650 return (ifile(drive), rel)
645 return (ifile(drive), rel)
651
646
652 def splitext(self):
647 def splitext(self):
653 (filename, ext) = path.path.splitext(self)
648 (filename, ext) = path.path.splitext(self)
654 return (ifile(filename), ext)
649 return (ifile(filename), ext)
655
650
656 if hasattr(path.path, "splitunc"):
651 if hasattr(path.path, "splitunc"):
657 def splitunc(self):
652 def splitunc(self):
658 (unc, rest) = path.path.splitunc(self)
653 (unc, rest) = path.path.splitunc(self)
659 return (ifile(unc), rest)
654 return (ifile(unc), rest)
660
655
661 def _get_uncshare(self):
656 def _get_uncshare(self):
662 unc, r = os.path.splitunc(self)
657 unc, r = os.path.splitunc(self)
663 return ifile(unc)
658 return ifile(unc)
664
659
665 uncshare = property(
660 uncshare = property(
666 _get_uncshare, None, None,
661 _get_uncshare, None, None,
667 """ The UNC mount point for this path.
662 """ The UNC mount point for this path.
668 This is empty for paths on local drives. """)
663 This is empty for paths on local drives. """)
669
664
670 def joinpath(self, *args):
665 def joinpath(self, *args):
671 return ifile(path.path.joinpath(self, *args))
666 return ifile(path.path.joinpath(self, *args))
672
667
673 def splitall(self):
668 def splitall(self):
674 return map(ifile, path.path.splitall(self))
669 return map(ifile, path.path.splitall(self))
675
670
676 def relpath(self):
671 def relpath(self):
677 return ifile(path.path.relpath(self))
672 return ifile(path.path.relpath(self))
678
673
679 def relpathto(self, dest):
674 def relpathto(self, dest):
680 return ifile(path.path.relpathto(self, dest))
675 return ifile(path.path.relpathto(self, dest))
681
676
682 def listdir(self, pattern=None):
677 def listdir(self, pattern=None):
683 return [ifile(child) for child in path.path.listdir(self, pattern)]
678 return [ifile(child) for child in path.path.listdir(self, pattern)]
684
679
685 def dirs(self, pattern=None):
680 def dirs(self, pattern=None):
686 return [ifile(child) for child in path.path.dirs(self, pattern)]
681 return [ifile(child) for child in path.path.dirs(self, pattern)]
687
682
688 def files(self, pattern=None):
683 def files(self, pattern=None):
689 return [ifile(child) for child in path.path.files(self, pattern)]
684 return [ifile(child) for child in path.path.files(self, pattern)]
690
685
691 def walk(self, pattern=None):
686 def walk(self, pattern=None):
692 for child in path.path.walk(self, pattern):
687 for child in path.path.walk(self, pattern):
693 yield ifile(child)
688 yield ifile(child)
694
689
695 def walkdirs(self, pattern=None):
690 def walkdirs(self, pattern=None):
696 for child in path.path.walkdirs(self, pattern):
691 for child in path.path.walkdirs(self, pattern):
697 yield ifile(child)
692 yield ifile(child)
698
693
699 def walkfiles(self, pattern=None):
694 def walkfiles(self, pattern=None):
700 for child in path.path.walkfiles(self, pattern):
695 for child in path.path.walkfiles(self, pattern):
701 yield ifile(child)
696 yield ifile(child)
702
697
703 def glob(self, pattern):
698 def glob(self, pattern):
704 return map(ifile, path.path.glob(self, pattern))
699 return map(ifile, path.path.glob(self, pattern))
705
700
706 if hasattr(os, 'readlink'):
701 if hasattr(os, 'readlink'):
707 def readlink(self):
702 def readlink(self):
708 return ifile(path.path.readlink(self))
703 return ifile(path.path.readlink(self))
709
704
710 def readlinkabs(self):
705 def readlinkabs(self):
711 return ifile(path.path.readlinkabs(self))
706 return ifile(path.path.readlinkabs(self))
712
707
713 def getmode(self):
708 def getmode(self):
714 return self.stat().st_mode
709 return self.stat().st_mode
715 mode = property(getmode, None, None, "Access mode")
710 mode = property(getmode, None, None, "Access mode")
716
711
717 def gettype(self):
712 def gettype(self):
718 data = [
713 data = [
719 (stat.S_ISREG, "file"),
714 (stat.S_ISREG, "file"),
720 (stat.S_ISDIR, "dir"),
715 (stat.S_ISDIR, "dir"),
721 (stat.S_ISCHR, "chardev"),
716 (stat.S_ISCHR, "chardev"),
722 (stat.S_ISBLK, "blockdev"),
717 (stat.S_ISBLK, "blockdev"),
723 (stat.S_ISFIFO, "fifo"),
718 (stat.S_ISFIFO, "fifo"),
724 (stat.S_ISLNK, "symlink"),
719 (stat.S_ISLNK, "symlink"),
725 (stat.S_ISSOCK,"socket"),
720 (stat.S_ISSOCK,"socket"),
726 ]
721 ]
727 lstat = self.lstat()
722 lstat = self.lstat()
728 if lstat is not None:
723 if lstat is not None:
729 types = set([text for (func, text) in data if func(lstat.st_mode)])
724 types = set([text for (func, text) in data if func(lstat.st_mode)])
730 else:
725 else:
731 types = set()
726 types = set()
732 m = self.mode
727 m = self.mode
733 types.update([text for (func, text) in data if func(m)])
728 types.update([text for (func, text) in data if func(m)])
734 return ", ".join(types)
729 return ", ".join(types)
735 type = property(gettype, None, None, "file type (file, directory, link, etc.)")
730 type = property(gettype, None, None, "file type (file, directory, link, etc.)")
736
731
737 def getmodestr(self):
732 def getmodestr(self):
738 m = self.mode
733 m = self.mode
739 data = [
734 data = [
740 (stat.S_IRUSR, "-r"),
735 (stat.S_IRUSR, "-r"),
741 (stat.S_IWUSR, "-w"),
736 (stat.S_IWUSR, "-w"),
742 (stat.S_IXUSR, "-x"),
737 (stat.S_IXUSR, "-x"),
743 (stat.S_IRGRP, "-r"),
738 (stat.S_IRGRP, "-r"),
744 (stat.S_IWGRP, "-w"),
739 (stat.S_IWGRP, "-w"),
745 (stat.S_IXGRP, "-x"),
740 (stat.S_IXGRP, "-x"),
746 (stat.S_IROTH, "-r"),
741 (stat.S_IROTH, "-r"),
747 (stat.S_IWOTH, "-w"),
742 (stat.S_IWOTH, "-w"),
748 (stat.S_IXOTH, "-x"),
743 (stat.S_IXOTH, "-x"),
749 ]
744 ]
750 return "".join([text[bool(m&bit)] for (bit, text) in data])
745 return "".join([text[bool(m&bit)] for (bit, text) in data])
751
746
752 modestr = property(getmodestr, None, None, "Access mode as string")
747 modestr = property(getmodestr, None, None, "Access mode as string")
753
748
754 def getblocks(self):
749 def getblocks(self):
755 return self.stat().st_blocks
750 return self.stat().st_blocks
756 blocks = property(getblocks, None, None, "File size in blocks")
751 blocks = property(getblocks, None, None, "File size in blocks")
757
752
758 def getblksize(self):
753 def getblksize(self):
759 return self.stat().st_blksize
754 return self.stat().st_blksize
760 blksize = property(getblksize, None, None, "Filesystem block size")
755 blksize = property(getblksize, None, None, "Filesystem block size")
761
756
762 def getdev(self):
757 def getdev(self):
763 return self.stat().st_dev
758 return self.stat().st_dev
764 dev = property(getdev)
759 dev = property(getdev)
765
760
766 def getnlink(self):
761 def getnlink(self):
767 return self.stat().st_nlink
762 return self.stat().st_nlink
768 nlink = property(getnlink, None, None, "Number of links")
763 nlink = property(getnlink, None, None, "Number of links")
769
764
770 def getuid(self):
765 def getuid(self):
771 return self.stat().st_uid
766 return self.stat().st_uid
772 uid = property(getuid, None, None, "User id of file owner")
767 uid = property(getuid, None, None, "User id of file owner")
773
768
774 def getgid(self):
769 def getgid(self):
775 return self.stat().st_gid
770 return self.stat().st_gid
776 gid = property(getgid, None, None, "Group id of file owner")
771 gid = property(getgid, None, None, "Group id of file owner")
777
772
778 def getowner(self):
773 def getowner(self):
779 stat = self.stat()
774 stat = self.stat()
780 try:
775 try:
781 return pwd.getpwuid(stat.st_uid).pw_name
776 return pwd.getpwuid(stat.st_uid).pw_name
782 except KeyError:
777 except KeyError:
783 return stat.st_uid
778 return stat.st_uid
784 owner = property(getowner, None, None, "Owner name (or id)")
779 owner = property(getowner, None, None, "Owner name (or id)")
785
780
786 def getgroup(self):
781 def getgroup(self):
787 stat = self.stat()
782 stat = self.stat()
788 try:
783 try:
789 return grp.getgrgid(stat.st_gid).gr_name
784 return grp.getgrgid(stat.st_gid).gr_name
790 except KeyError:
785 except KeyError:
791 return stat.st_gid
786 return stat.st_gid
792 group = property(getgroup, None, None, "Group name (or id)")
787 group = property(getgroup, None, None, "Group name (or id)")
793
788
794 def getadate(self):
789 def getadate(self):
795 return datetime.datetime.utcfromtimestamp(self.atime)
790 return datetime.datetime.utcfromtimestamp(self.atime)
796 adate = property(getadate, None, None, "Access date")
791 adate = property(getadate, None, None, "Access date")
797
792
798 def getcdate(self):
793 def getcdate(self):
799 return datetime.datetime.utcfromtimestamp(self.ctime)
794 return datetime.datetime.utcfromtimestamp(self.ctime)
800 cdate = property(getcdate, None, None, "Creation date")
795 cdate = property(getcdate, None, None, "Creation date")
801
796
802 def getmdate(self):
797 def getmdate(self):
803 return datetime.datetime.utcfromtimestamp(self.mtime)
798 return datetime.datetime.utcfromtimestamp(self.mtime)
804 mdate = property(getmdate, None, None, "Modification date")
799 mdate = property(getmdate, None, None, "Modification date")
805
800
806 def getmimetype(self):
801 def getmimetype(self):
807 return mimetypes.guess_type(self.basename())[0]
802 return mimetypes.guess_type(self.basename())[0]
808 mimetype = property(getmimetype, None, None, "MIME type")
803 mimetype = property(getmimetype, None, None, "MIME type")
809
804
810 def getencoding(self):
805 def getencoding(self):
811 return mimetypes.guess_type(self.basename())[1]
806 return mimetypes.guess_type(self.basename())[1]
812 encoding = property(getencoding, None, None, "Compression")
807 encoding = property(getencoding, None, None, "Compression")
813
808
814 def __repr__(self):
809 def __repr__(self):
815 return "ifile(%s)" % path._base.__repr__(self)
810 return "ifile(%s)" % path._base.__repr__(self)
816
811
817 defaultattrs = (None, "type", "size", "modestr", "owner", "group", "mdate")
812 defaultattrs = (None, "type", "size", "modestr", "owner", "group", "mdate")
818
813
819 def __xattrs__(self, mode):
814 def __xattrs__(self, mode):
820 if mode == "detail":
815 if mode == "detail":
821 return (
816 return (
822 "name", "basename()", "abspath()", "realpath()",
817 "name", "basename()", "abspath()", "realpath()",
823 "type", "mode", "modestr", "stat()", "lstat()",
818 "type", "mode", "modestr", "stat()", "lstat()",
824 "uid", "gid", "owner", "group", "dev", "nlink",
819 "uid", "gid", "owner", "group", "dev", "nlink",
825 "ctime", "mtime", "atime", "cdate", "mdate", "adate",
820 "ctime", "mtime", "atime", "cdate", "mdate", "adate",
826 "size", "blocks", "blksize", "isdir()", "islink()",
821 "size", "blocks", "blksize", "isdir()", "islink()",
827 "mimetype", "encoding"
822 "mimetype", "encoding"
828 )
823 )
829 return self.defaultattrs
824 return self.defaultattrs
830
825
831 def __xrepr__(self, mode):
826 def __xrepr__(self, mode):
832 yield (-1, True)
827 yield (-1, True)
833 try:
828 try:
834 if self.isdir():
829 if self.isdir():
835 name = "idir"
830 name = "idir"
836 style = astyle.style_dir
831 style = astyle.style_dir
837 else:
832 else:
838 name = "ifile"
833 name = "ifile"
839 style = astyle.style_file
834 style = astyle.style_file
840 except IOError:
835 except IOError:
841 name = "ifile"
836 name = "ifile"
842 style = astyle.style_default
837 style = astyle.style_default
843 if mode == "cell" or mode in "header" or mode == "footer":
838 if mode == "cell" or mode in "header" or mode == "footer":
844 abspath = repr(path._base(self.normpath()))
839 abspath = repr(path._base(self.normpath()))
845 if abspath.startswith("u"):
840 if abspath.startswith("u"):
846 abspath = abspath[2:-1]
841 abspath = abspath[2:-1]
847 else:
842 else:
848 abspath = abspath[1:-1]
843 abspath = abspath[1:-1]
849 if mode == "cell":
844 if mode == "cell":
850 yield (style, abspath)
845 yield (style, abspath)
851 else:
846 else:
852 yield (style, "%s(%s)" % (name, abspath))
847 yield (style, "%s(%s)" % (name, abspath))
853 else:
848 else:
854 yield (style, repr(self))
849 yield (style, repr(self))
855
850
856 def __xiter__(self, mode):
851 def __xiter__(self, mode):
857 if self.isdir():
852 if self.isdir():
858 yield iparentdir(self / os.pardir)
853 yield iparentdir(self / os.pardir)
859 for child in sorted(self.listdir()):
854 for child in sorted(self.listdir()):
860 yield child
855 yield child
861 else:
856 else:
862 f = self.open("rb")
857 f = self.open("rb")
863 for line in f:
858 for line in f:
864 yield line
859 yield line
865 f.close()
860 f.close()
866
861
867
862
868 class iparentdir(ifile):
863 class iparentdir(ifile):
869 def __xrepr__(self, mode):
864 def __xrepr__(self, mode):
870 yield (-1, True)
865 yield (-1, True)
871 if mode == "cell":
866 if mode == "cell":
872 yield (astyle.style_dir, os.pardir)
867 yield (astyle.style_dir, os.pardir)
873 else:
868 else:
874 for part in ifile.__xrepr__(self, mode):
869 for part in ifile.__xrepr__(self, mode):
875 yield part
870 yield part
876
871
877
872
878 class ils(Table):
873 class ils(Table):
879 """
874 """
880 List the current (or a specific) directory.
875 List the current (or a specific) directory.
881
876
882 Examples:
877 Examples:
883
878
884 >>> ils
879 >>> ils
885 >>> ils("/usr/local/lib/python2.4")
880 >>> ils("/usr/local/lib/python2.4")
886 >>> ils("~")
881 >>> ils("~")
887 """
882 """
888 def __init__(self, base=os.curdir):
883 def __init__(self, base=os.curdir):
889 self.base = os.path.expanduser(base)
884 self.base = os.path.expanduser(base)
890
885
891 def __xiter__(self, mode):
886 def __xiter__(self, mode):
892 return xiter(ifile(self.base), mode)
887 return xiter(ifile(self.base), mode)
893
888
894 def __xrepr__(self, mode):
889 def __xrepr__(self, mode):
895 return ifile(self.base).__xrepr__(mode)
890 return ifile(self.base).__xrepr__(mode)
896
891
897 def __repr__(self):
892 def __repr__(self):
898 return "%s.%s(%r)" % \
893 return "%s.%s(%r)" % \
899 (self.__class__.__module__, self.__class__.__name__, self.base)
894 (self.__class__.__module__, self.__class__.__name__, self.base)
900
895
901
896
902 class iglob(Table):
897 class iglob(Table):
903 """
898 """
904 List all files and directories matching a specified pattern.
899 List all files and directories matching a specified pattern.
905 (See ``glob.glob()`` for more info.).
900 (See ``glob.glob()`` for more info.).
906
901
907 Examples:
902 Examples:
908
903
909 >>> iglob("*.py")
904 >>> iglob("*.py")
910 """
905 """
911 def __init__(self, glob):
906 def __init__(self, glob):
912 self.glob = glob
907 self.glob = glob
913
908
914 def __xiter__(self, mode):
909 def __xiter__(self, mode):
915 for name in glob.glob(self.glob):
910 for name in glob.glob(self.glob):
916 yield ifile(name)
911 yield ifile(name)
917
912
918 def __xrepr__(self, mode):
913 def __xrepr__(self, mode):
919 yield (-1, True)
914 yield (-1, True)
920 if mode == "header" or mode == "footer" or mode == "cell":
915 if mode == "header" or mode == "footer" or mode == "cell":
921 yield (astyle.style_default,
916 yield (astyle.style_default,
922 "%s(%r)" % (self.__class__.__name__, self.glob))
917 "%s(%r)" % (self.__class__.__name__, self.glob))
923 else:
918 else:
924 yield (astyle.style_default, repr(self))
919 yield (astyle.style_default, repr(self))
925
920
926 def __repr__(self):
921 def __repr__(self):
927 return "%s.%s(%r)" % \
922 return "%s.%s(%r)" % \
928 (self.__class__.__module__, self.__class__.__name__, self.glob)
923 (self.__class__.__module__, self.__class__.__name__, self.glob)
929
924
930
925
931 class iwalk(Table):
926 class iwalk(Table):
932 """
927 """
933 List all files and directories in a directory and it's subdirectory.
928 List all files and directories in a directory and it's subdirectory.
934
929
935 >>> iwalk
930 >>> iwalk
936 >>> iwalk("/usr/local/lib/python2.4")
931 >>> iwalk("/usr/local/lib/python2.4")
937 >>> iwalk("~")
932 >>> iwalk("~")
938 """
933 """
939 def __init__(self, base=os.curdir, dirs=True, files=True):
934 def __init__(self, base=os.curdir, dirs=True, files=True):
940 self.base = os.path.expanduser(base)
935 self.base = os.path.expanduser(base)
941 self.dirs = dirs
936 self.dirs = dirs
942 self.files = files
937 self.files = files
943
938
944 def __xiter__(self, mode):
939 def __xiter__(self, mode):
945 for (dirpath, dirnames, filenames) in os.walk(self.base):
940 for (dirpath, dirnames, filenames) in os.walk(self.base):
946 if self.dirs:
941 if self.dirs:
947 for name in sorted(dirnames):
942 for name in sorted(dirnames):
948 yield ifile(os.path.join(dirpath, name))
943 yield ifile(os.path.join(dirpath, name))
949 if self.files:
944 if self.files:
950 for name in sorted(filenames):
945 for name in sorted(filenames):
951 yield ifile(os.path.join(dirpath, name))
946 yield ifile(os.path.join(dirpath, name))
952
947
953 def __xrepr__(self, mode):
948 def __xrepr__(self, mode):
954 yield (-1, True)
949 yield (-1, True)
955 if mode == "header" or mode == "footer" or mode == "cell":
950 if mode == "header" or mode == "footer" or mode == "cell":
956 yield (astyle.style_default,
951 yield (astyle.style_default,
957 "%s(%r)" % (self.__class__.__name__, self.base))
952 "%s(%r)" % (self.__class__.__name__, self.base))
958 else:
953 else:
959 yield (astyle.style_default, repr(self))
954 yield (astyle.style_default, repr(self))
960
955
961 def __repr__(self):
956 def __repr__(self):
962 return "%s.%s(%r)" % \
957 return "%s.%s(%r)" % \
963 (self.__class__.__module__, self.__class__.__name__, self.base)
958 (self.__class__.__module__, self.__class__.__name__, self.base)
964
959
965
960
966 class ipwdentry(object):
961 class ipwdentry(object):
967 """
962 """
968 ``ipwdentry`` objects encapsulate entries in the Unix user account and
963 ``ipwdentry`` objects encapsulate entries in the Unix user account and
969 password database.
964 password database.
970 """
965 """
971 def __init__(self, id):
966 def __init__(self, id):
972 self._id = id
967 self._id = id
973 self._entry = None
968 self._entry = None
974
969
975 def _getentry(self):
970 def _getentry(self):
976 if self._entry is None:
971 if self._entry is None:
977 if isinstance(self._id, basestring):
972 if isinstance(self._id, basestring):
978 self._entry = pwd.getpwnam(self._id)
973 self._entry = pwd.getpwnam(self._id)
979 else:
974 else:
980 self._entry = pwd.getpwuid(self._id)
975 self._entry = pwd.getpwuid(self._id)
981 return self._entry
976 return self._entry
982
977
983 def getname(self):
978 def getname(self):
984 if isinstance(self._id, basestring):
979 if isinstance(self._id, basestring):
985 return self._id
980 return self._id
986 else:
981 else:
987 return self._getentry().pw_name
982 return self._getentry().pw_name
988 name = property(getname, None, None, "User name")
983 name = property(getname, None, None, "User name")
989
984
990 def getpasswd(self):
985 def getpasswd(self):
991 return self._getentry().pw_passwd
986 return self._getentry().pw_passwd
992 passwd = property(getpasswd, None, None, "Password")
987 passwd = property(getpasswd, None, None, "Password")
993
988
994 def getuid(self):
989 def getuid(self):
995 if isinstance(self._id, basestring):
990 if isinstance(self._id, basestring):
996 return self._getentry().pw_uid
991 return self._getentry().pw_uid
997 else:
992 else:
998 return self._id
993 return self._id
999 uid = property(getuid, None, None, "User id")
994 uid = property(getuid, None, None, "User id")
1000
995
1001 def getgid(self):
996 def getgid(self):
1002 return self._getentry().pw_gid
997 return self._getentry().pw_gid
1003 gid = property(getgid, None, None, "Primary group id")
998 gid = property(getgid, None, None, "Primary group id")
1004
999
1005 def getgroup(self):
1000 def getgroup(self):
1006 return igrpentry(self.gid)
1001 return igrpentry(self.gid)
1007 group = property(getgroup, None, None, "Group")
1002 group = property(getgroup, None, None, "Group")
1008
1003
1009 def getgecos(self):
1004 def getgecos(self):
1010 return self._getentry().pw_gecos
1005 return self._getentry().pw_gecos
1011 gecos = property(getgecos, None, None, "Information (e.g. full user name)")
1006 gecos = property(getgecos, None, None, "Information (e.g. full user name)")
1012
1007
1013 def getdir(self):
1008 def getdir(self):
1014 return self._getentry().pw_dir
1009 return self._getentry().pw_dir
1015 dir = property(getdir, None, None, "$HOME directory")
1010 dir = property(getdir, None, None, "$HOME directory")
1016
1011
1017 def getshell(self):
1012 def getshell(self):
1018 return self._getentry().pw_shell
1013 return self._getentry().pw_shell
1019 shell = property(getshell, None, None, "Login shell")
1014 shell = property(getshell, None, None, "Login shell")
1020
1015
1021 def __xattrs__(self, mode):
1016 def __xattrs__(self, mode):
1022 return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell")
1017 return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell")
1023
1018
1024 def __repr__(self):
1019 def __repr__(self):
1025 return "%s.%s(%r)" % \
1020 return "%s.%s(%r)" % \
1026 (self.__class__.__module__, self.__class__.__name__, self._id)
1021 (self.__class__.__module__, self.__class__.__name__, self._id)
1027
1022
1028
1023
1029 class ipwd(Table):
1024 class ipwd(Table):
1030 """
1025 """
1031 List all entries in the Unix user account and password database.
1026 List all entries in the Unix user account and password database.
1032
1027
1033 Example:
1028 Example:
1034
1029
1035 >>> ipwd | isort("uid")
1030 >>> ipwd | isort("uid")
1036 """
1031 """
1037 def __iter__(self):
1032 def __iter__(self):
1038 for entry in pwd.getpwall():
1033 for entry in pwd.getpwall():
1039 yield ipwdentry(entry.pw_name)
1034 yield ipwdentry(entry.pw_name)
1040
1035
1041 def __xrepr__(self, mode):
1036 def __xrepr__(self, mode):
1042 yield (-1, True)
1037 yield (-1, True)
1043 if mode == "header" or mode == "footer" or mode == "cell":
1038 if mode == "header" or mode == "footer" or mode == "cell":
1044 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1039 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1045 else:
1040 else:
1046 yield (astyle.style_default, repr(self))
1041 yield (astyle.style_default, repr(self))
1047
1042
1048
1043
1049 class igrpentry(object):
1044 class igrpentry(object):
1050 """
1045 """
1051 ``igrpentry`` objects encapsulate entries in the Unix group database.
1046 ``igrpentry`` objects encapsulate entries in the Unix group database.
1052 """
1047 """
1053 def __init__(self, id):
1048 def __init__(self, id):
1054 self._id = id
1049 self._id = id
1055 self._entry = None
1050 self._entry = None
1056
1051
1057 def _getentry(self):
1052 def _getentry(self):
1058 if self._entry is None:
1053 if self._entry is None:
1059 if isinstance(self._id, basestring):
1054 if isinstance(self._id, basestring):
1060 self._entry = grp.getgrnam(self._id)
1055 self._entry = grp.getgrnam(self._id)
1061 else:
1056 else:
1062 self._entry = grp.getgrgid(self._id)
1057 self._entry = grp.getgrgid(self._id)
1063 return self._entry
1058 return self._entry
1064
1059
1065 def getname(self):
1060 def getname(self):
1066 if isinstance(self._id, basestring):
1061 if isinstance(self._id, basestring):
1067 return self._id
1062 return self._id
1068 else:
1063 else:
1069 return self._getentry().gr_name
1064 return self._getentry().gr_name
1070 name = property(getname, None, None, "Group name")
1065 name = property(getname, None, None, "Group name")
1071
1066
1072 def getpasswd(self):
1067 def getpasswd(self):
1073 return self._getentry().gr_passwd
1068 return self._getentry().gr_passwd
1074 passwd = property(getpasswd, None, None, "Password")
1069 passwd = property(getpasswd, None, None, "Password")
1075
1070
1076 def getgid(self):
1071 def getgid(self):
1077 if isinstance(self._id, basestring):
1072 if isinstance(self._id, basestring):
1078 return self._getentry().gr_gid
1073 return self._getentry().gr_gid
1079 else:
1074 else:
1080 return self._id
1075 return self._id
1081 gid = property(getgid, None, None, "Group id")
1076 gid = property(getgid, None, None, "Group id")
1082
1077
1083 def getmem(self):
1078 def getmem(self):
1084 return self._getentry().gr_mem
1079 return self._getentry().gr_mem
1085 mem = property(getmem, None, None, "Members")
1080 mem = property(getmem, None, None, "Members")
1086
1081
1087 def __xattrs__(self, mode):
1082 def __xattrs__(self, mode):
1088 return ("name", "passwd", "gid", "mem")
1083 return ("name", "passwd", "gid", "mem")
1089
1084
1090 def __xrepr__(self, mode):
1085 def __xrepr__(self, mode):
1091 yield (-1, True)
1086 yield (-1, True)
1092 if mode == "header" or mode == "footer" or mode == "cell":
1087 if mode == "header" or mode == "footer" or mode == "cell":
1093 yield (astyle.style_default, "group ")
1088 yield (astyle.style_default, "group ")
1094 try:
1089 try:
1095 yield (astyle.style_default, self.name)
1090 yield (astyle.style_default, self.name)
1096 except KeyError:
1091 except KeyError:
1097 if isinstance(self._id, basestring):
1092 if isinstance(self._id, basestring):
1098 yield (astyle.style_default, self.name_id)
1093 yield (astyle.style_default, self.name_id)
1099 else:
1094 else:
1100 yield (astyle.style_type_number, str(self._id))
1095 yield (astyle.style_type_number, str(self._id))
1101 else:
1096 else:
1102 yield (astyle.style_default, repr(self))
1097 yield (astyle.style_default, repr(self))
1103
1098
1104 def __xiter__(self, mode):
1099 def __xiter__(self, mode):
1105 for member in self.mem:
1100 for member in self.mem:
1106 yield ipwdentry(member)
1101 yield ipwdentry(member)
1107
1102
1108 def __repr__(self):
1103 def __repr__(self):
1109 return "%s.%s(%r)" % \
1104 return "%s.%s(%r)" % \
1110 (self.__class__.__module__, self.__class__.__name__, self._id)
1105 (self.__class__.__module__, self.__class__.__name__, self._id)
1111
1106
1112
1107
1113 class igrp(Table):
1108 class igrp(Table):
1114 """
1109 """
1115 This ``Table`` lists all entries in the Unix group database.
1110 This ``Table`` lists all entries in the Unix group database.
1116 """
1111 """
1117 def __xiter__(self, mode):
1112 def __xiter__(self, mode):
1118 for entry in grp.getgrall():
1113 for entry in grp.getgrall():
1119 yield igrpentry(entry.gr_name)
1114 yield igrpentry(entry.gr_name)
1120
1115
1121 def __xrepr__(self, mode):
1116 def __xrepr__(self, mode):
1122 yield (-1, False)
1117 yield (-1, False)
1123 if mode == "header" or mode == "footer":
1118 if mode == "header" or mode == "footer":
1124 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1119 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1125 else:
1120 else:
1126 yield (astyle.style_default, repr(self))
1121 yield (astyle.style_default, repr(self))
1127
1122
1128
1123
1129 class Fields(object):
1124 class Fields(object):
1130 def __init__(self, fieldnames, **fields):
1125 def __init__(self, fieldnames, **fields):
1131 self.__fieldnames = fieldnames
1126 self.__fieldnames = fieldnames
1132 for (key, value) in fields.iteritems():
1127 for (key, value) in fields.iteritems():
1133 setattr(self, key, value)
1128 setattr(self, key, value)
1134
1129
1135 def __xattrs__(self, mode):
1130 def __xattrs__(self, mode):
1136 return self.__fieldnames
1131 return self.__fieldnames
1137
1132
1138 def __xrepr__(self, mode):
1133 def __xrepr__(self, mode):
1139 yield (-1, False)
1134 yield (-1, False)
1140 if mode == "header" or mode == "cell":
1135 if mode == "header" or mode == "cell":
1141 yield (astyle.style_default, self.__class__.__name__)
1136 yield (astyle.style_default, self.__class__.__name__)
1142 yield (astyle.style_default, "(")
1137 yield (astyle.style_default, "(")
1143 for (i, f) in enumerate(self.__fieldnames):
1138 for (i, f) in enumerate(self.__fieldnames):
1144 if i:
1139 if i:
1145 yield (astyle.style_default, ", ")
1140 yield (astyle.style_default, ", ")
1146 yield (astyle.style_default, f)
1141 yield (astyle.style_default, f)
1147 yield (astyle.style_default, "=")
1142 yield (astyle.style_default, "=")
1148 for part in xrepr(getattr(self, f), "default"):
1143 for part in xrepr(getattr(self, f), "default"):
1149 yield part
1144 yield part
1150 yield (astyle.style_default, ")")
1145 yield (astyle.style_default, ")")
1151 elif mode == "footer":
1146 elif mode == "footer":
1152 yield (astyle.style_default, self.__class__.__name__)
1147 yield (astyle.style_default, self.__class__.__name__)
1153 yield (astyle.style_default, "(")
1148 yield (astyle.style_default, "(")
1154 for (i, f) in enumerate(self.__fieldnames):
1149 for (i, f) in enumerate(self.__fieldnames):
1155 if i:
1150 if i:
1156 yield (astyle.style_default, ", ")
1151 yield (astyle.style_default, ", ")
1157 yield (astyle.style_default, f)
1152 yield (astyle.style_default, f)
1158 yield (astyle.style_default, ")")
1153 yield (astyle.style_default, ")")
1159 else:
1154 else:
1160 yield (astyle.style_default, repr(self))
1155 yield (astyle.style_default, repr(self))
1161
1156
1162
1157
1163 class FieldTable(Table, list):
1158 class FieldTable(Table, list):
1164 def __init__(self, *fields):
1159 def __init__(self, *fields):
1165 Table.__init__(self)
1160 Table.__init__(self)
1166 list.__init__(self)
1161 list.__init__(self)
1167 self.fields = fields
1162 self.fields = fields
1168
1163
1169 def add(self, **fields):
1164 def add(self, **fields):
1170 self.append(Fields(self.fields, **fields))
1165 self.append(Fields(self.fields, **fields))
1171
1166
1172 def __xiter__(self, mode):
1167 def __xiter__(self, mode):
1173 return list.__iter__(self)
1168 return list.__iter__(self)
1174
1169
1175 def __xrepr__(self, mode):
1170 def __xrepr__(self, mode):
1176 yield (-1, False)
1171 yield (-1, False)
1177 if mode == "header" or mode == "footer":
1172 if mode == "header" or mode == "footer":
1178 yield (astyle.style_default, self.__class__.__name__)
1173 yield (astyle.style_default, self.__class__.__name__)
1179 yield (astyle.style_default, "(")
1174 yield (astyle.style_default, "(")
1180 for (i, f) in enumerate(self.__fieldnames):
1175 for (i, f) in enumerate(self.__fieldnames):
1181 if i:
1176 if i:
1182 yield (astyle.style_default, ", ")
1177 yield (astyle.style_default, ", ")
1183 yield (astyle.style_default, f)
1178 yield (astyle.style_default, f)
1184 yield (astyle.style_default, ")")
1179 yield (astyle.style_default, ")")
1185 else:
1180 else:
1186 yield (astyle.style_default, repr(self))
1181 yield (astyle.style_default, repr(self))
1187
1182
1188 def __repr__(self):
1183 def __repr__(self):
1189 return "<%s.%s object with fields=%r at 0x%x>" % \
1184 return "<%s.%s object with fields=%r at 0x%x>" % \
1190 (self.__class__.__module__, self.__class__.__name__,
1185 (self.__class__.__module__, self.__class__.__name__,
1191 ", ".join(map(repr, self.fields)), id(self))
1186 ", ".join(map(repr, self.fields)), id(self))
1192
1187
1193
1188
1194 class List(list):
1189 class List(list):
1195 def __xattrs__(self, mode):
1190 def __xattrs__(self, mode):
1196 return xrange(len(self))
1191 return xrange(len(self))
1197
1192
1198 def __xrepr__(self, mode):
1193 def __xrepr__(self, mode):
1199 yield (-1, False)
1194 yield (-1, False)
1200 if mode == "header" or mode == "cell" or mode == "footer" or mode == "default":
1195 if mode == "header" or mode == "cell" or mode == "footer" or mode == "default":
1201 yield (astyle.style_default, self.__class__.__name__)
1196 yield (astyle.style_default, self.__class__.__name__)
1202 yield (astyle.style_default, "(")
1197 yield (astyle.style_default, "(")
1203 for (i, item) in enumerate(self):
1198 for (i, item) in enumerate(self):
1204 if i:
1199 if i:
1205 yield (astyle.style_default, ", ")
1200 yield (astyle.style_default, ", ")
1206 for part in xrepr(item, "default"):
1201 for part in xrepr(item, "default"):
1207 yield part
1202 yield part
1208 yield (astyle.style_default, ")")
1203 yield (astyle.style_default, ")")
1209 else:
1204 else:
1210 yield (astyle.style_default, repr(self))
1205 yield (astyle.style_default, repr(self))
1211
1206
1212
1207
1213 class ienv(Table):
1208 class ienv(Table):
1214 """
1209 """
1215 List environment variables.
1210 List environment variables.
1216
1211
1217 Example:
1212 Example:
1218
1213
1219 >>> ienv
1214 >>> ienv
1220 """
1215 """
1221
1216
1222 def __xiter__(self, mode):
1217 def __xiter__(self, mode):
1223 fields = ("key", "value")
1218 fields = ("key", "value")
1224 for (key, value) in os.environ.iteritems():
1219 for (key, value) in os.environ.iteritems():
1225 yield Fields(fields, key=key, value=value)
1220 yield Fields(fields, key=key, value=value)
1226
1221
1227 def __xrepr__(self, mode):
1222 def __xrepr__(self, mode):
1228 yield (-1, True)
1223 yield (-1, True)
1229 if mode == "header" or mode == "cell":
1224 if mode == "header" or mode == "cell":
1230 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1225 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1231 else:
1226 else:
1232 yield (astyle.style_default, repr(self))
1227 yield (astyle.style_default, repr(self))
1233
1228
1234
1229
1235 class icsv(Pipe):
1230 class icsv(Pipe):
1236 """
1231 """
1237 This ``Pipe`` lists turn the input (with must be a pipe outputting lines
1232 This ``Pipe`` lists turn the input (with must be a pipe outputting lines
1238 or an ``ifile``) into lines of CVS columns.
1233 or an ``ifile``) into lines of CVS columns.
1239 """
1234 """
1240 def __init__(self, **csvargs):
1235 def __init__(self, **csvargs):
1241 """
1236 """
1242 Create an ``icsv`` object. ``cvsargs`` will be passed through as
1237 Create an ``icsv`` object. ``cvsargs`` will be passed through as
1243 keyword arguments to ``cvs.reader()``.
1238 keyword arguments to ``cvs.reader()``.
1244 """
1239 """
1245 self.csvargs = csvargs
1240 self.csvargs = csvargs
1246
1241
1247 def __xiter__(self, mode):
1242 def __xiter__(self, mode):
1248 input = self.input
1243 input = self.input
1249 if isinstance(input, ifile):
1244 if isinstance(input, ifile):
1250 input = input.open("rb")
1245 input = input.open("rb")
1251 reader = csv.reader(input, **self.csvargs)
1246 reader = csv.reader(input, **self.csvargs)
1252 for line in reader:
1247 for line in reader:
1253 yield List(line)
1248 yield List(line)
1254
1249
1255 def __xrepr__(self, mode):
1250 def __xrepr__(self, mode):
1256 yield (-1, False)
1251 yield (-1, False)
1257 if mode == "header" or mode == "footer":
1252 if mode == "header" or mode == "footer":
1258 input = getattr(self, "input", None)
1253 input = getattr(self, "input", None)
1259 if input is not None:
1254 if input is not None:
1260 for part in xrepr(input, mode):
1255 for part in xrepr(input, mode):
1261 yield part
1256 yield part
1262 yield (astyle.style_default, " | ")
1257 yield (astyle.style_default, " | ")
1263 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1258 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1264 for (i, (name, value)) in enumerate(self.csvargs.iteritems()):
1259 for (i, (name, value)) in enumerate(self.csvargs.iteritems()):
1265 if i:
1260 if i:
1266 yield (astyle.style_default, ", ")
1261 yield (astyle.style_default, ", ")
1267 yield (astyle.style_default, name)
1262 yield (astyle.style_default, name)
1268 yield (astyle.style_default, "=")
1263 yield (astyle.style_default, "=")
1269 for part in xrepr(value, "default"):
1264 for part in xrepr(value, "default"):
1270 yield part
1265 yield part
1271 yield (astyle.style_default, ")")
1266 yield (astyle.style_default, ")")
1272 else:
1267 else:
1273 yield (astyle.style_default, repr(self))
1268 yield (astyle.style_default, repr(self))
1274
1269
1275 def __repr__(self):
1270 def __repr__(self):
1276 args = ", ".join(["%s=%r" % item for item in self.csvargs.iteritems()])
1271 args = ", ".join(["%s=%r" % item for item in self.csvargs.iteritems()])
1277 return "<%s.%s %s at 0x%x>" % \
1272 return "<%s.%s %s at 0x%x>" % \
1278 (self.__class__.__module__, self.__class__.__name__, args, id(self))
1273 (self.__class__.__module__, self.__class__.__name__, args, id(self))
1279
1274
1280
1275
1281 class ix(Table):
1276 class ix(Table):
1282 """
1277 """
1283 Execute a system command and list its output as lines
1278 Execute a system command and list its output as lines
1284 (similar to ``os.popen()``).
1279 (similar to ``os.popen()``).
1285
1280
1286 Examples:
1281 Examples:
1287
1282
1288 >>> ix("ps x")
1283 >>> ix("ps x")
1289 >>> ix("find .") | ifile
1284 >>> ix("find .") | ifile
1290 """
1285 """
1291 def __init__(self, cmd):
1286 def __init__(self, cmd):
1292 self.cmd = cmd
1287 self.cmd = cmd
1293 self._pipe = None
1288 self._pipe = None
1294
1289
1295 def __xiter__(self, mode):
1290 def __xiter__(self, mode):
1296 self._pipe = os.popen(self.cmd)
1291 self._pipe = os.popen(self.cmd)
1297 for l in self._pipe:
1292 for l in self._pipe:
1298 yield l.rstrip("\r\n")
1293 yield l.rstrip("\r\n")
1299 self._pipe.close()
1294 self._pipe.close()
1300 self._pipe = None
1295 self._pipe = None
1301
1296
1302 def __del__(self):
1297 def __del__(self):
1303 if self._pipe is not None and not self._pipe.closed:
1298 if self._pipe is not None and not self._pipe.closed:
1304 self._pipe.close()
1299 self._pipe.close()
1305 self._pipe = None
1300 self._pipe = None
1306
1301
1307 def __xrepr__(self, mode):
1302 def __xrepr__(self, mode):
1308 yield (-1, True)
1303 yield (-1, True)
1309 if mode == "header" or mode == "footer":
1304 if mode == "header" or mode == "footer":
1310 yield (astyle.style_default,
1305 yield (astyle.style_default,
1311 "%s(%r)" % (self.__class__.__name__, self.cmd))
1306 "%s(%r)" % (self.__class__.__name__, self.cmd))
1312 else:
1307 else:
1313 yield (astyle.style_default, repr(self))
1308 yield (astyle.style_default, repr(self))
1314
1309
1315 def __repr__(self):
1310 def __repr__(self):
1316 return "%s.%s(%r)" % \
1311 return "%s.%s(%r)" % \
1317 (self.__class__.__module__, self.__class__.__name__, self.cmd)
1312 (self.__class__.__module__, self.__class__.__name__, self.cmd)
1318
1313
1319
1314
1320 class ifilter(Pipe):
1315 class ifilter(Pipe):
1321 """
1316 """
1322 Filter an input pipe. Only objects where an expression evaluates to true
1317 Filter an input pipe. Only objects where an expression evaluates to true
1323 (and doesn't raise an exception) are listed.
1318 (and doesn't raise an exception) are listed.
1324
1319
1325 Examples:
1320 Examples:
1326
1321
1327 >>> ils | ifilter("_.isfile() and size>1000")
1322 >>> ils | ifilter("_.isfile() and size>1000")
1328 >>> igrp | ifilter("len(mem)")
1323 >>> igrp | ifilter("len(mem)")
1329 >>> sys.modules | ifilter(lambda _:_.value is not None)
1324 >>> sys.modules | ifilter(lambda _:_.value is not None)
1330 """
1325 """
1331
1326
1332 def __init__(self, expr, errors="raiseifallfail"):
1327 def __init__(self, expr, errors="raiseifallfail"):
1333 """
1328 """
1334 Create an ``ifilter`` object. ``expr`` can be a callable or a string
1329 Create an ``ifilter`` object. ``expr`` can be a callable or a string
1335 containing an expression. ``errors`` specifies how exception during
1330 containing an expression. ``errors`` specifies how exception during
1336 evaluation of ``expr`` are handled:
1331 evaluation of ``expr`` are handled:
1337
1332
1338 * ``drop``: drop all items that have errors;
1333 * ``drop``: drop all items that have errors;
1339
1334
1340 * ``keep``: keep all items that have errors;
1335 * ``keep``: keep all items that have errors;
1341
1336
1342 * ``keeperror``: keep the exception of all items that have errors;
1337 * ``keeperror``: keep the exception of all items that have errors;
1343
1338
1344 * ``raise``: raise the exception;
1339 * ``raise``: raise the exception;
1345
1340
1346 * ``raiseifallfail``: raise the first exception if all items have errors;
1341 * ``raiseifallfail``: raise the first exception if all items have errors;
1347 otherwise drop those with errors (this is the default).
1342 otherwise drop those with errors (this is the default).
1348 """
1343 """
1349 self.expr = expr
1344 self.expr = expr
1350 self.errors = errors
1345 self.errors = errors
1351
1346
1352 def __xiter__(self, mode):
1347 def __xiter__(self, mode):
1353 if callable(self.expr):
1348 if callable(self.expr):
1354 def test(item):
1349 def test(item):
1355 return self.expr(item)
1350 return self.expr(item)
1356 else:
1351 else:
1357 def test(item):
1352 def test(item):
1358 return eval(self.expr, globals(), _AttrNamespace(item))
1353 return eval(self.expr, globals(), AttrNamespace(item))
1359
1354
1360 ok = 0
1355 ok = 0
1361 exc_info = None
1356 exc_info = None
1362 for item in xiter(self.input, mode):
1357 for item in xiter(self.input, mode):
1363 try:
1358 try:
1364 if test(item):
1359 if test(item):
1365 yield item
1360 yield item
1366 ok += 1
1361 ok += 1
1367 except (KeyboardInterrupt, SystemExit):
1362 except (KeyboardInterrupt, SystemExit):
1368 raise
1363 raise
1369 except Exception, exc:
1364 except Exception, exc:
1370 if self.errors == "drop":
1365 if self.errors == "drop":
1371 pass # Ignore errors
1366 pass # Ignore errors
1372 elif self.errors == "keep":
1367 elif self.errors == "keep":
1373 yield item
1368 yield item
1374 elif self.errors == "keeperror":
1369 elif self.errors == "keeperror":
1375 yield exc
1370 yield exc
1376 elif self.errors == "raise":
1371 elif self.errors == "raise":
1377 raise
1372 raise
1378 elif self.errors == "raiseifallfail":
1373 elif self.errors == "raiseifallfail":
1379 if exc_info is None:
1374 if exc_info is None:
1380 exc_info = sys.exc_info()
1375 exc_info = sys.exc_info()
1381 if not ok and exc_info is not None:
1376 if not ok and exc_info is not None:
1382 raise exc_info[0], exc_info[1], exc_info[2]
1377 raise exc_info[0], exc_info[1], exc_info[2]
1383
1378
1384 def __xrepr__(self, mode):
1379 def __xrepr__(self, mode):
1385 yield (-1, True)
1380 yield (-1, True)
1386 if mode == "header" or mode == "footer":
1381 if mode == "header" or mode == "footer":
1387 input = getattr(self, "input", None)
1382 input = getattr(self, "input", None)
1388 if input is not None:
1383 if input is not None:
1389 for part in xrepr(input, mode):
1384 for part in xrepr(input, mode):
1390 yield part
1385 yield part
1391 yield (astyle.style_default, " | ")
1386 yield (astyle.style_default, " | ")
1392 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1387 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1393 for part in xrepr(self.expr, "default"):
1388 for part in xrepr(self.expr, "default"):
1394 yield part
1389 yield part
1395 yield (astyle.style_default, ")")
1390 yield (astyle.style_default, ")")
1396 else:
1391 else:
1397 yield (astyle.style_default, repr(self))
1392 yield (astyle.style_default, repr(self))
1398
1393
1399 def __repr__(self):
1394 def __repr__(self):
1400 return "<%s.%s expr=%r at 0x%x>" % \
1395 return "<%s.%s expr=%r at 0x%x>" % \
1401 (self.__class__.__module__, self.__class__.__name__,
1396 (self.__class__.__module__, self.__class__.__name__,
1402 self.expr, id(self))
1397 self.expr, id(self))
1403
1398
1404
1399
1405 class ieval(Pipe):
1400 class ieval(Pipe):
1406 """
1401 """
1407 Evaluate an expression for each object in the input pipe.
1402 Evaluate an expression for each object in the input pipe.
1408
1403
1409 Examples:
1404 Examples:
1410
1405
1411 >>> ils | ieval("_.abspath()")
1406 >>> ils | ieval("_.abspath()")
1412 >>> sys.path | ieval(ifile)
1407 >>> sys.path | ieval(ifile)
1413 """
1408 """
1414
1409
1415 def __init__(self, expr, errors="raiseifallfail"):
1410 def __init__(self, expr, errors="raiseifallfail"):
1416 """
1411 """
1417 Create an ``ieval`` object. ``expr`` can be a callable or a string
1412 Create an ``ieval`` object. ``expr`` can be a callable or a string
1418 containing an expression. For the meaning of ``errors`` see ``ifilter``.
1413 containing an expression. For the meaning of ``errors`` see ``ifilter``.
1419 """
1414 """
1420 self.expr = expr
1415 self.expr = expr
1421 self.errors = errors
1416 self.errors = errors
1422
1417
1423 def __xiter__(self, mode):
1418 def __xiter__(self, mode):
1424 if callable(self.expr):
1419 if callable(self.expr):
1425 def do(item):
1420 def do(item):
1426 return self.expr(item)
1421 return self.expr(item)
1427 else:
1422 else:
1428 def do(item):
1423 def do(item):
1429 return eval(self.expr, globals(), _AttrNamespace(item))
1424 return eval(self.expr, globals(), AttrNamespace(item))
1430
1425
1431 ok = 0
1426 ok = 0
1432 exc_info = None
1427 exc_info = None
1433 for item in xiter(self.input, mode):
1428 for item in xiter(self.input, mode):
1434 try:
1429 try:
1435 yield do(item)
1430 yield do(item)
1436 except (KeyboardInterrupt, SystemExit):
1431 except (KeyboardInterrupt, SystemExit):
1437 raise
1432 raise
1438 except Exception, exc:
1433 except Exception, exc:
1439 if self.errors == "drop":
1434 if self.errors == "drop":
1440 pass # Ignore errors
1435 pass # Ignore errors
1441 elif self.errors == "keep":
1436 elif self.errors == "keep":
1442 yield item
1437 yield item
1443 elif self.errors == "keeperror":
1438 elif self.errors == "keeperror":
1444 yield exc
1439 yield exc
1445 elif self.errors == "raise":
1440 elif self.errors == "raise":
1446 raise
1441 raise
1447 elif self.errors == "raiseifallfail":
1442 elif self.errors == "raiseifallfail":
1448 if exc_info is None:
1443 if exc_info is None:
1449 exc_info = sys.exc_info()
1444 exc_info = sys.exc_info()
1450 if not ok and exc_info is not None:
1445 if not ok and exc_info is not None:
1451 raise exc_info[0], exc_info[1], exc_info[2]
1446 raise exc_info[0], exc_info[1], exc_info[2]
1452
1447
1453 def __xrepr__(self, mode):
1448 def __xrepr__(self, mode):
1454 yield (-1, True)
1449 yield (-1, True)
1455 if mode == "header" or mode == "footer":
1450 if mode == "header" or mode == "footer":
1456 input = getattr(self, "input", None)
1451 input = getattr(self, "input", None)
1457 if input is not None:
1452 if input is not None:
1458 for part in xrepr(input, mode):
1453 for part in xrepr(input, mode):
1459 yield part
1454 yield part
1460 yield (astyle.style_default, " | ")
1455 yield (astyle.style_default, " | ")
1461 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1456 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1462 for part in xrepr(self.expr, "default"):
1457 for part in xrepr(self.expr, "default"):
1463 yield part
1458 yield part
1464 yield (astyle.style_default, ")")
1459 yield (astyle.style_default, ")")
1465 else:
1460 else:
1466 yield (astyle.style_default, repr(self))
1461 yield (astyle.style_default, repr(self))
1467
1462
1468 def __repr__(self):
1463 def __repr__(self):
1469 return "<%s.%s expr=%r at 0x%x>" % \
1464 return "<%s.%s expr=%r at 0x%x>" % \
1470 (self.__class__.__module__, self.__class__.__name__,
1465 (self.__class__.__module__, self.__class__.__name__,
1471 self.expr, id(self))
1466 self.expr, id(self))
1472
1467
1473
1468
1474 class ienum(Pipe):
1469 class ienum(Pipe):
1475 """
1470 """
1476 Enumerate the input pipe (i.e. wrap each input object in an object
1471 Enumerate the input pipe (i.e. wrap each input object in an object
1477 with ``index`` and ``object`` attributes).
1472 with ``index`` and ``object`` attributes).
1478
1473
1479 Examples:
1474 Examples:
1480
1475
1481 >>> xrange(20) | ieval("_,_*_") | ienum | ifilter("index % 2 == 0") | ieval("object")
1476 >>> xrange(20) | ieval("_,_*_") | ienum | ifilter("index % 2 == 0") | ieval("object")
1482 """
1477 """
1483 def __xiter__(self, mode):
1478 def __xiter__(self, mode):
1484 fields = ("index", "object")
1479 fields = ("index", "object")
1485 for (index, object) in enumerate(xiter(self.input, mode)):
1480 for (index, object) in enumerate(xiter(self.input, mode)):
1486 yield Fields(fields, index=index, object=object)
1481 yield Fields(fields, index=index, object=object)
1487
1482
1488
1483
1489 class isort(Pipe):
1484 class isort(Pipe):
1490 """
1485 """
1491 Sorts the input pipe.
1486 Sorts the input pipe.
1492
1487
1493 Examples:
1488 Examples:
1494
1489
1495 >>> ils | isort("size")
1490 >>> ils | isort("size")
1496 >>> ils | isort("_.isdir(), _.lower()", reverse=True)
1491 >>> ils | isort("_.isdir(), _.lower()", reverse=True)
1497 """
1492 """
1498
1493
1499 def __init__(self, key, reverse=False):
1494 def __init__(self, key, reverse=False):
1500 """
1495 """
1501 Create an ``isort`` object. ``key`` can be a callable or a string
1496 Create an ``isort`` object. ``key`` can be a callable or a string
1502 containing an expression. If ``reverse`` is true the sort order will
1497 containing an expression. If ``reverse`` is true the sort order will
1503 be reversed.
1498 be reversed.
1504 """
1499 """
1505 self.key = key
1500 self.key = key
1506 self.reverse = reverse
1501 self.reverse = reverse
1507
1502
1508 def __xiter__(self, mode):
1503 def __xiter__(self, mode):
1509 if callable(self.key):
1504 if callable(self.key):
1510 items = sorted(
1505 items = sorted(
1511 xiter(self.input, mode),
1506 xiter(self.input, mode),
1512 key=self.key,
1507 key=self.key,
1513 reverse=self.reverse
1508 reverse=self.reverse
1514 )
1509 )
1515 else:
1510 else:
1516 def key(item):
1511 def key(item):
1517 return eval(self.key, globals(), _AttrNamespace(item))
1512 return eval(self.key, globals(), AttrNamespace(item))
1518 items = sorted(
1513 items = sorted(
1519 xiter(self.input, mode),
1514 xiter(self.input, mode),
1520 key=key,
1515 key=key,
1521 reverse=self.reverse
1516 reverse=self.reverse
1522 )
1517 )
1523 for item in items:
1518 for item in items:
1524 yield item
1519 yield item
1525
1520
1526 def __xrepr__(self, mode):
1521 def __xrepr__(self, mode):
1527 yield (-1, True)
1522 yield (-1, True)
1528 if mode == "header" or mode == "footer":
1523 if mode == "header" or mode == "footer":
1529 input = getattr(self, "input", None)
1524 input = getattr(self, "input", None)
1530 if input is not None:
1525 if input is not None:
1531 for part in xrepr(input, mode):
1526 for part in xrepr(input, mode):
1532 yield part
1527 yield part
1533 yield (astyle.style_default, " | ")
1528 yield (astyle.style_default, " | ")
1534 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1529 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1535 for part in xrepr(self.key, "default"):
1530 for part in xrepr(self.key, "default"):
1536 yield part
1531 yield part
1537 if self.reverse:
1532 if self.reverse:
1538 yield (astyle.style_default, ", ")
1533 yield (astyle.style_default, ", ")
1539 for part in xrepr(True, "default"):
1534 for part in xrepr(True, "default"):
1540 yield part
1535 yield part
1541 yield (astyle.style_default, ")")
1536 yield (astyle.style_default, ")")
1542 else:
1537 else:
1543 yield (astyle.style_default, repr(self))
1538 yield (astyle.style_default, repr(self))
1544
1539
1545 def __repr__(self):
1540 def __repr__(self):
1546 return "<%s.%s key=%r reverse=%r at 0x%x>" % \
1541 return "<%s.%s key=%r reverse=%r at 0x%x>" % \
1547 (self.__class__.__module__, self.__class__.__name__,
1542 (self.__class__.__module__, self.__class__.__name__,
1548 self.key, self.reverse, id(self))
1543 self.key, self.reverse, id(self))
1549
1544
1550
1545
1551 tab = 3 # for expandtabs()
1546 tab = 3 # for expandtabs()
1552
1547
1553 def _format(field):
1548 def _format(field):
1554 if isinstance(field, str):
1549 if isinstance(field, str):
1555 text = repr(field.expandtabs(tab))[1:-1]
1550 text = repr(field.expandtabs(tab))[1:-1]
1556 elif isinstance(field, unicode):
1551 elif isinstance(field, unicode):
1557 text = repr(field.expandtabs(tab))[2:-1]
1552 text = repr(field.expandtabs(tab))[2:-1]
1558 elif isinstance(field, datetime.datetime):
1553 elif isinstance(field, datetime.datetime):
1559 # Don't use strftime() here, as this requires year >= 1900
1554 # Don't use strftime() here, as this requires year >= 1900
1560 text = "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
1555 text = "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
1561 (field.year, field.month, field.day,
1556 (field.year, field.month, field.day,
1562 field.hour, field.minute, field.second, field.microsecond)
1557 field.hour, field.minute, field.second, field.microsecond)
1563 elif isinstance(field, datetime.date):
1558 elif isinstance(field, datetime.date):
1564 text = "%04d-%02d-%02d" % (field.year, field.month, field.day)
1559 text = "%04d-%02d-%02d" % (field.year, field.month, field.day)
1565 else:
1560 else:
1566 text = repr(field)
1561 text = repr(field)
1567 return text
1562 return text
1568
1563
1569
1564
1570 class Display(object):
1565 class Display(object):
1571 class __metaclass__(type):
1566 class __metaclass__(type):
1572 def __ror__(self, input):
1567 def __ror__(self, input):
1573 return input | self()
1568 return input | self()
1574
1569
1575 def __ror__(self, input):
1570 def __ror__(self, input):
1576 self.input = input
1571 self.input = input
1577 return self
1572 return self
1578
1573
1579 def display(self):
1574 def display(self):
1580 pass
1575 pass
1581
1576
1582
1577
1583 class iless(Display):
1578 class iless(Display):
1584 cmd = "less --quit-if-one-screen --LONG-PROMPT --LINE-NUMBERS --chop-long-lines --shift=8 --RAW-CONTROL-CHARS"
1579 cmd = "less --quit-if-one-screen --LONG-PROMPT --LINE-NUMBERS --chop-long-lines --shift=8 --RAW-CONTROL-CHARS"
1585
1580
1586 def display(self):
1581 def display(self):
1587 try:
1582 try:
1588 pager = os.popen(self.cmd, "w")
1583 pager = os.popen(self.cmd, "w")
1589 try:
1584 try:
1590 for item in xiter(self.input, "default"):
1585 for item in xiter(self.input, "default"):
1591 attrs = xattrs(item, "default")
1586 attrs = xattrs(item, "default")
1592 attrs = ["%s=%s" % (a, _format(_getattr(item, a))) for a in attrs]
1587 attrs = ["%s=%s" % (a, _format(_getattr(item, a))) for a in attrs]
1593 pager.write(" ".join(attrs))
1588 pager.write(" ".join(attrs))
1594 pager.write("\n")
1589 pager.write("\n")
1595 finally:
1590 finally:
1596 pager.close()
1591 pager.close()
1597 except Exception, exc:
1592 except Exception, exc:
1598 print "%s: %s" % (exc.__class__.__name__, str(exc))
1593 print "%s: %s" % (exc.__class__.__name__, str(exc))
1599
1594
1600
1595
1601 def xformat(value, mode, maxlength):
1596 def xformat(value, mode, maxlength):
1602 align = None
1597 align = None
1603 full = False
1598 full = False
1604 width = 0
1599 width = 0
1605 text = astyle.Text()
1600 text = astyle.Text()
1606 for part in xrepr(value, mode):
1601 for part in xrepr(value, mode):
1607 # part is (alignment, stop)
1602 # part is (alignment, stop)
1608 if isinstance(part[0], int):
1603 if isinstance(part[0], int):
1609 # only consider the first occurence
1604 # only consider the first occurence
1610 if align is None:
1605 if align is None:
1611 align = part[0]
1606 align = part[0]
1612 full = part[1]
1607 full = part[1]
1613 # part is (style, text)
1608 # part is (style, text)
1614 else:
1609 else:
1615 text.append(part)
1610 text.append(part)
1616 width += len(part[1])
1611 width += len(part[1])
1617 if width >= maxlength and not full:
1612 if width >= maxlength and not full:
1618 text.append((astyle.style_ellisis, "..."))
1613 text.append((astyle.style_ellisis, "..."))
1619 width += 3
1614 width += 3
1620 break
1615 break
1621 if align is None: # default to left alignment
1616 if align is None: # default to left alignment
1622 align = -1
1617 align = -1
1623 return (align, width, text)
1618 return (align, width, text)
1624
1619
1625
1620
1626 class idump(Display):
1621 class idump(Display):
1627 # The approximate maximum length of a column entry
1622 # The approximate maximum length of a column entry
1628 maxattrlength = 200
1623 maxattrlength = 200
1629
1624
1630 # Style for column names
1625 # Style for column names
1631 style_header = astyle.Style.fromstr("white:black:bold")
1626 style_header = astyle.Style.fromstr("white:black:bold")
1632
1627
1633 def __init__(self, *attrs):
1628 def __init__(self, *attrs):
1634 self.attrs = attrs
1629 self.attrs = attrs
1635 self.headerpadchar = " "
1630 self.headerpadchar = " "
1636 self.headersepchar = "|"
1631 self.headersepchar = "|"
1637 self.datapadchar = " "
1632 self.datapadchar = " "
1638 self.datasepchar = "|"
1633 self.datasepchar = "|"
1639
1634
1640 def display(self):
1635 def display(self):
1641 stream = genutils.Term.cout
1636 stream = genutils.Term.cout
1642 allattrs = []
1637 allattrs = []
1643 allattrset = set()
1638 allattrset = set()
1644 colwidths = {}
1639 colwidths = {}
1645 rows = []
1640 rows = []
1646 for item in xiter(self.input, "default"):
1641 for item in xiter(self.input, "default"):
1647 row = {}
1642 row = {}
1648 attrs = self.attrs
1643 attrs = self.attrs
1649 if not attrs:
1644 if not attrs:
1650 attrs = xattrs(item, "default")
1645 attrs = xattrs(item, "default")
1651 for attrname in attrs:
1646 for attrname in attrs:
1652 if attrname not in allattrset:
1647 if attrname not in allattrset:
1653 allattrs.append(attrname)
1648 allattrs.append(attrname)
1654 allattrset.add(attrname)
1649 allattrset.add(attrname)
1655 colwidths[attrname] = len(_attrname(attrname))
1650 colwidths[attrname] = len(_attrname(attrname))
1656 try:
1651 try:
1657 value = _getattr(item, attrname, None)
1652 value = _getattr(item, attrname, None)
1658 except (KeyboardInterrupt, SystemExit):
1653 except (KeyboardInterrupt, SystemExit):
1659 raise
1654 raise
1660 except Exception, exc:
1655 except Exception, exc:
1661 value = exc
1656 value = exc
1662 (align, width, text) = xformat(value, "cell", self.maxattrlength)
1657 (align, width, text) = xformat(value, "cell", self.maxattrlength)
1663 colwidths[attrname] = max(colwidths[attrname], width)
1658 colwidths[attrname] = max(colwidths[attrname], width)
1664 # remember alignment, length and colored parts
1659 # remember alignment, length and colored parts
1665 row[attrname] = (align, width, text)
1660 row[attrname] = (align, width, text)
1666 rows.append(row)
1661 rows.append(row)
1667
1662
1668 stream.write("\n")
1663 stream.write("\n")
1669 for (i, attrname) in enumerate(allattrs):
1664 for (i, attrname) in enumerate(allattrs):
1670 self.style_header(_attrname(attrname)).write(stream)
1665 self.style_header(_attrname(attrname)).write(stream)
1671 spc = colwidths[attrname] - len(_attrname(attrname))
1666 spc = colwidths[attrname] - len(_attrname(attrname))
1672 if i < len(colwidths)-1:
1667 if i < len(colwidths)-1:
1673 stream.write(self.headerpadchar*spc)
1668 stream.write(self.headerpadchar*spc)
1674 stream.write(self.headersepchar)
1669 stream.write(self.headersepchar)
1675 stream.write("\n")
1670 stream.write("\n")
1676
1671
1677 for row in rows:
1672 for row in rows:
1678 for (i, attrname) in enumerate(allattrs):
1673 for (i, attrname) in enumerate(allattrs):
1679 (align, width, text) = row[attrname]
1674 (align, width, text) = row[attrname]
1680 spc = colwidths[attrname] - width
1675 spc = colwidths[attrname] - width
1681 if align == -1:
1676 if align == -1:
1682 text.write(stream)
1677 text.write(stream)
1683 if i < len(colwidths)-1:
1678 if i < len(colwidths)-1:
1684 stream.write(self.datapadchar*spc)
1679 stream.write(self.datapadchar*spc)
1685 elif align == 0:
1680 elif align == 0:
1686 spc = colwidths[attrname] - width
1681 spc = colwidths[attrname] - width
1687 spc1 = spc//2
1682 spc1 = spc//2
1688 spc2 = spc-spc1
1683 spc2 = spc-spc1
1689 stream.write(self.datapadchar*spc1)
1684 stream.write(self.datapadchar*spc1)
1690 text.write(stream)
1685 text.write(stream)
1691 if i < len(colwidths)-1:
1686 if i < len(colwidths)-1:
1692 stream.write(self.datapadchar*spc2)
1687 stream.write(self.datapadchar*spc2)
1693 else:
1688 else:
1694 stream.write(self.datapadchar*spc)
1689 stream.write(self.datapadchar*spc)
1695 text.write(stream)
1690 text.write(stream)
1696 if i < len(colwidths)-1:
1691 if i < len(colwidths)-1:
1697 stream.write(self.datasepchar)
1692 stream.write(self.datasepchar)
1698 stream.write("\n")
1693 stream.write("\n")
1699
1694
1700
1695
1701 class XMode(object):
1696 class XMode(object):
1702 """
1697 """
1703 An ``XMode`` object describes one enter mode available for an object
1698 An ``XMode`` object describes one enter mode available for an object
1704 """
1699 """
1705 def __init__(self, object, mode, title=None, description=None):
1700 def __init__(self, object, mode, title=None, description=None):
1706 """
1701 """
1707 Create a new ``XMode`` object for the object ``object``. This object
1702 Create a new ``XMode`` object for the object ``object``. This object
1708 must support the enter mode ``mode`` (i.e. ``object.__xiter__(mode)``
1703 must support the enter mode ``mode`` (i.e. ``object.__xiter__(mode)``
1709 must return an iterable). ``title`` and ``description`` will be
1704 must return an iterable). ``title`` and ``description`` will be
1710 displayed in the browser when selecting among the available modes.
1705 displayed in the browser when selecting among the available modes.
1711 """
1706 """
1712 self.object = object
1707 self.object = object
1713 self.mode = mode
1708 self.mode = mode
1714 self.title = title
1709 self.title = title
1715 self.description = description
1710 self.description = description
1716
1711
1717 def __repr__(self):
1712 def __repr__(self):
1718 return "<%s.%s object mode=%r at 0x%x>" % \
1713 return "<%s.%s object mode=%r at 0x%x>" % \
1719 (self.__class__.__module__, self.__class__.__name__,
1714 (self.__class__.__module__, self.__class__.__name__,
1720 self.mode, id(self))
1715 self.mode, id(self))
1721
1716
1722 def __xrepr__(self, mode):
1717 def __xrepr__(self, mode):
1723 yield (-1, True)
1718 yield (-1, True)
1724 if mode == "header" or mode == "footer":
1719 if mode == "header" or mode == "footer":
1725 yield (astyle.style_default, self.title)
1720 yield (astyle.style_default, self.title)
1726 else:
1721 else:
1727 yield (astyle.style_default, repr(self))
1722 yield (astyle.style_default, repr(self))
1728
1723
1729 def __xattrs__(self, mode):
1724 def __xattrs__(self, mode):
1730 if mode == "detail":
1725 if mode == "detail":
1731 return ("object", "mode", "title", "description")
1726 return ("object", "mode", "title", "description")
1732 return ("title", "description")
1727 return ("title", "description")
1733
1728
1734 def __xiter__(self, mode):
1729 def __xiter__(self, mode):
1735 return xiter(self.object, self.mode)
1730 return xiter(self.object, self.mode)
1736
1731
1737
1732
1738 class XAttr(object):
1733 class XAttr(object):
1739 def __init__(self, object, name):
1734 def __init__(self, object, name):
1740 self.name = _attrname(name)
1735 self.name = _attrname(name)
1741
1736
1742 try:
1737 try:
1743 self.value = _getattr(object, name)
1738 self.value = _getattr(object, name)
1744 except (KeyboardInterrupt, SystemExit):
1739 except (KeyboardInterrupt, SystemExit):
1745 raise
1740 raise
1746 except Exception, exc:
1741 except Exception, exc:
1747 if exc.__class__.__module__ == "exceptions":
1742 if exc.__class__.__module__ == "exceptions":
1748 self.value = exc.__class__.__name__
1743 self.value = exc.__class__.__name__
1749 else:
1744 else:
1750 self.value = "%s.%s" % \
1745 self.value = "%s.%s" % \
1751 (exc.__class__.__module__, exc.__class__.__name__)
1746 (exc.__class__.__module__, exc.__class__.__name__)
1752 self.type = self.value
1747 self.type = self.value
1753 else:
1748 else:
1754 t = type(self.value)
1749 t = type(self.value)
1755 if t.__module__ == "__builtin__":
1750 if t.__module__ == "__builtin__":
1756 self.type = t.__name__
1751 self.type = t.__name__
1757 else:
1752 else:
1758 self.type = "%s.%s" % (t.__module__, t.__name__)
1753 self.type = "%s.%s" % (t.__module__, t.__name__)
1759
1754
1760 doc = None
1755 doc = None
1761 if isinstance(name, basestring):
1756 if isinstance(name, basestring):
1762 if name.endswith("()"):
1757 if name.endswith("()"):
1763 doc = getattr(getattr(object, name[:-2]), "__doc__", None)
1758 doc = getattr(getattr(object, name[:-2]), "__doc__", None)
1764 else:
1759 else:
1765 try:
1760 try:
1766 meta = getattr(type(object), name)
1761 meta = getattr(type(object), name)
1767 except AttributeError:
1762 except AttributeError:
1768 pass
1763 pass
1769 else:
1764 else:
1770 if isinstance(meta, property):
1765 if isinstance(meta, property):
1771 doc = getattr(meta, "__doc__", None)
1766 doc = getattr(meta, "__doc__", None)
1772 elif callable(name):
1767 elif callable(name):
1773 doc = getattr(name, "__doc__", None)
1768 doc = getattr(name, "__doc__", None)
1774 if isinstance(doc, basestring):
1769 if isinstance(doc, basestring):
1775 doc = doc.strip()
1770 doc = doc.strip()
1776 self.doc = doc
1771 self.doc = doc
1777
1772
1778 def __xattrs__(self, mode):
1773 def __xattrs__(self, mode):
1779 return ("name", "type", "doc", "value")
1774 return ("name", "type", "doc", "value")
1780
1775
1781
1776
1782 _ibrowse_help = """
1777 try:
1783 down
1778 from ibrowse import ibrowse
1784 Move the cursor to the next line.
1779 except ImportError:
1785
1786 up
1787 Move the cursor to the previous line.
1788
1789 pagedown
1790 Move the cursor down one page (minus overlap).
1791
1792 pageup
1793 Move the cursor up one page (minus overlap).
1794
1795 left
1796 Move the cursor left.
1797
1798 right
1799 Move the cursor right.
1800
1801 home
1802 Move the cursor to the first column.
1803
1804 end
1805 Move the cursor to the last column.
1806
1807 prevattr
1808 Move the cursor one attribute column to the left.
1809
1810 nextattr
1811 Move the cursor one attribute column to the right.
1812
1813 pick
1814 'Pick' the object under the cursor (i.e. the row the cursor is on). This
1815 leaves the browser and returns the picked object to the caller. (In IPython
1816 this object will be available as the '_' variable.)
1817
1818 pickattr
1819 'Pick' the attribute under the cursor (i.e. the row/column the cursor is on).
1820
1821 pickallattrs
1822 Pick' the complete column under the cursor (i.e. the attribute under the
1823 cursor) from all currently fetched objects. These attributes will be returned
1824 as a list.
1825
1826 tooglemark
1827 Mark/unmark the object under the cursor. Marked objects have a '!' after the
1828 row number).
1829
1830 pickmarked
1831 'Pick' marked objects. Marked objects will be returned as a list.
1832
1833 pickmarkedattr
1834 'Pick' the attribute under the cursor from all marked objects (This returns a
1835 list).
1836
1837 enterdefault
1838 Enter the object under the cursor. (what this mean depends on the object
1839 itself (i.e. how it implements the '__xiter__' method). This opens a new
1840 browser 'level'.
1841
1842 enter
1843 Enter the object under the cursor. If the object provides different enter
1844 modes a menu of all modes will be presented; choose one and enter it (via the
1845 'enter' or 'enterdefault' command).
1846
1847 enterattr
1848 Enter the attribute under the cursor.
1849
1850 leave
1851 Leave the current browser level and go back to the previous one.
1852
1853 detail
1854 Show a detail view of the object under the cursor. This shows the name, type,
1855 doc string and value of the object attributes (and it might show more
1856 attributes than in the list view, depending on the object).
1857
1858 detailattr
1859 Show a detail view of the attribute under the cursor.
1860
1861 markrange
1862 Mark all objects from the last marked object before the current cursor
1863 position to the cursor position.
1864
1865 sortattrasc
1866 Sort the objects (in ascending order) using the attribute under the cursor as
1867 the sort key.
1868
1869 sortattrdesc
1870 Sort the objects (in descending order) using the attribute under the cursor as
1871 the sort key.
1872
1873 goto
1874 Jump to a row. The row number can be entered at the bottom of the screen.
1875
1876 find
1877 Search forward for a row. At the bottom of the screen the condition can be
1878 entered.
1879
1880 findbackwards
1881 Search backward for a row. At the bottom of the screen the condition can be
1882 entered.
1883
1884 help
1885 This screen.
1886 """
1887
1888
1889 if curses is not None:
1890 class UnassignedKeyError(Exception):
1891 """
1892 Exception that is used for reporting unassigned keys.
1893 """
1894
1895
1896 class UnknownCommandError(Exception):
1897 """
1898 Exception that is used for reporting unknown command (this should never
1899 happen).
1900 """
1901
1902
1903 class CommandError(Exception):
1904 """
1905 Exception that is used for reporting that a command can't be executed.
1906 """
1907
1908
1909 class _BrowserCachedItem(object):
1910 # This is used internally by ``ibrowse`` to store a item together with its
1911 # marked status.
1912 __slots__ = ("item", "marked")
1913
1914 def __init__(self, item):
1915 self.item = item
1916 self.marked = False
1917
1918
1919 class _BrowserHelp(object):
1920 style_header = astyle.Style.fromstr("red:blacK")
1921 # This is used internally by ``ibrowse`` for displaying the help screen.
1922 def __init__(self, browser):
1923 self.browser = browser
1924
1925 def __xrepr__(self, mode):
1926 yield (-1, True)
1927 if mode == "header" or mode == "footer":
1928 yield (astyle.style_default, "ibrowse help screen")
1929 else:
1930 yield (astyle.style_default, repr(self))
1931
1932 def __xiter__(self, mode):
1933 # Get reverse key mapping
1934 allkeys = {}
1935 for (key, cmd) in self.browser.keymap.iteritems():
1936 allkeys.setdefault(cmd, []).append(key)
1937
1938 fields = ("key", "description")
1939
1940 for (i, command) in enumerate(_ibrowse_help.strip().split("\n\n")):
1941 if i:
1942 yield Fields(fields, key="", description="")
1943
1944 (name, description) = command.split("\n", 1)
1945 keys = allkeys.get(name, [])
1946 lines = textwrap.wrap(description, 60)
1947
1948 yield Fields(fields, description=astyle.Text((self.style_header, name)))
1949 for i in xrange(max(len(keys), len(lines))):
1950 try:
1951 key = self.browser.keylabel(keys[i])
1952 except IndexError:
1953 key = ""
1954 try:
1955 line = lines[i]
1956 except IndexError:
1957 line = ""
1958 yield Fields(fields, key=key, description=line)
1959
1960
1961 class _BrowserLevel(object):
1962 # This is used internally to store the state (iterator, fetch items,
1963 # position of cursor and screen, etc.) of one browser level
1964 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
1965 # a stack.
1966 def __init__(self, browser, input, iterator, mainsizey, *attrs):
1967 self.browser = browser
1968 self.input = input
1969 self.header = [x for x in xrepr(input, "header") if not isinstance(x[0], int)]
1970 # iterator for the input
1971 self.iterator = iterator
1972
1973 # is the iterator exhausted?
1974 self.exhausted = False
1975
1976 # attributes to be display (autodetected if empty)
1977 self.attrs = attrs
1978
1979 # fetched items (+ marked flag)
1980 self.items = deque()
1981
1982 # Number of marked objects
1983 self.marked = 0
1984
1985 # Vertical cursor position
1986 self.cury = 0
1987
1988 # Horizontal cursor position
1989 self.curx = 0
1990
1991 # Index of first data column
1992 self.datastartx = 0
1993
1994 # Index of first data line
1995 self.datastarty = 0
1996
1997 # height of the data display area
1998 self.mainsizey = mainsizey
1999
2000 # width of the data display area (changes when scrolling)
2001 self.mainsizex = 0
2002
2003 # Size of row number (changes when scrolling)
2004 self.numbersizex = 0
2005
2006 # Attribute names to display (in this order)
2007 self.displayattrs = []
2008
2009 # index and name of attribute under the cursor
2010 self.displayattr = (None, _default)
2011
2012 # Maps attribute names to column widths
2013 self.colwidths = {}
2014
2015 self.fetch(mainsizey)
2016 self.calcdisplayattrs()
2017 # formatted attributes for the items on screen
2018 # (i.e. self.items[self.datastarty:self.datastarty+self.mainsizey])
2019 self.displayrows = [self.getrow(i) for i in xrange(len(self.items))]
2020 self.calcwidths()
2021 self.calcdisplayattr()
2022
2023 def fetch(self, count):
2024 # Try to fill ``self.items`` with at least ``count`` objects.
2025 have = len(self.items)
2026 while not self.exhausted and have < count:
2027 try:
2028 item = self.iterator.next()
2029 except StopIteration:
2030 self.exhausted = True
2031 break
2032 else:
2033 have += 1
2034 self.items.append(_BrowserCachedItem(item))
2035
2036 def calcdisplayattrs(self):
2037 # Calculate which attributes are available from the objects that are
2038 # currently visible on screen (and store it in ``self.displayattrs``)
2039 attrnames = set()
2040 # If the browser object specifies a fixed list of attributes,
2041 # simply use it.
2042 if self.attrs:
2043 self.displayattrs = self.attrs
2044 else:
2045 self.displayattrs = []
2046 endy = min(self.datastarty+self.mainsizey, len(self.items))
2047 for i in xrange(self.datastarty, endy):
2048 for attrname in xattrs(self.items[i].item, "default"):
2049 if attrname not in attrnames:
2050 self.displayattrs.append(attrname)
2051 attrnames.add(attrname)
2052
2053 def getrow(self, i):
2054 # Return a dictinary with the attributes for the object
2055 # ``self.items[i]``. Attribute names are taken from
2056 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
2057 # called before.
2058 row = {}
2059 item = self.items[i].item
2060 for attrname in self.displayattrs:
2061 try:
2062 value = _getattr(item, attrname, _default)
2063 except (KeyboardInterrupt, SystemExit):
2064 raise
2065 except Exception, exc:
2066 value = exc
2067 # only store attribute if it exists (or we got an exception)
2068 if value is not _default:
2069 parts = []
2070 totallength = 0
2071 align = None
2072 full = False
2073 # Collect parts until we have enough
2074 for part in xrepr(value, "cell"):
2075 # part gives (alignment, stop)
2076 # instead of (style, text)
2077 if isinstance(part[0], int):
2078 # only consider the first occurence
2079 if align is None:
2080 align = part[0]
2081 full = part[1]
2082 else:
2083 parts.append(part)
2084 totallength += len(part[1])
2085 if totallength >= self.browser.maxattrlength and not full:
2086 parts.append((astyle.style_ellisis, "..."))
2087 totallength += 3
2088 break
2089 # remember alignment, length and colored parts
2090 row[attrname] = (align, totallength, parts)
2091 return row
2092
2093 def calcwidths(self):
2094 # Recalculate the displayed fields and their width.
2095 # ``calcdisplayattrs()'' must have been called and the cache
2096 # for attributes of the objects on screen (``self.displayrows``)
2097 # must have been filled. This returns a dictionary mapping
2098 # colmn names to width.
2099 self.colwidths = {}
2100 for row in self.displayrows:
2101 for attrname in self.displayattrs:
2102 try:
2103 length = row[attrname][1]
2104 except KeyError:
2105 length = 0
2106 # always add attribute to colwidths, even if it doesn't exist
2107 if attrname not in self.colwidths:
2108 self.colwidths[attrname] = len(_attrname(attrname))
2109 newwidth = max(self.colwidths[attrname], length)
2110 self.colwidths[attrname] = newwidth
2111
2112 # How many characters do we need to paint the item number?
2113 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
2114 # How must space have we got to display data?
2115 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
2116 # width of all columns
2117 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
2118
2119 def calcdisplayattr(self):
2120 # Find out on which attribute the cursor is on and store this
2121 # information in ``self.displayattr``.
2122 pos = 0
2123 for (i, attrname) in enumerate(self.displayattrs):
2124 if pos+self.colwidths[attrname] >= self.curx:
2125 self.displayattr = (i, attrname)
2126 break
2127 pos += self.colwidths[attrname]+1
2128 else:
2129 self.displayattr = (None, _default)
2130
2131 def moveto(self, x, y, refresh=False):
2132 # Move the cursor to the position ``(x,y)`` (in data coordinates,
2133 # not in screen coordinates). If ``refresh`` is true, all cached
2134 # values will be recalculated (e.g. because the list has been
2135 # resorted, so screen positions etc. are no longer valid).
2136 olddatastarty = self.datastarty
2137 oldx = self.curx
2138 oldy = self.cury
2139 x = int(x+0.5)
2140 y = int(y+0.5)
2141 newx = x # remember where we wanted to move
2142 newy = y # remember where we wanted to move
2143
2144 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
2145 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
2146
2147 # Make sure that the cursor didn't leave the main area vertically
2148 if y < 0:
2149 y = 0
2150 self.fetch(y+scrollbordery+1) # try to get more items
2151 if y >= len(self.items):
2152 y = max(0, len(self.items)-1)
2153
2154 # Make sure that the cursor stays on screen vertically
2155 if y < self.datastarty+scrollbordery:
2156 self.datastarty = max(0, y-scrollbordery)
2157 elif y >= self.datastarty+self.mainsizey-scrollbordery:
2158 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
2159 len(self.items)-self.mainsizey))
2160
2161 if refresh: # Do we need to refresh the complete display?
2162 self.calcdisplayattrs()
2163 endy = min(self.datastarty+self.mainsizey, len(self.items))
2164 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
2165 self.calcwidths()
2166 # Did we scroll vertically => update displayrows
2167 # and various other attributes
2168 elif self.datastarty != olddatastarty:
2169 # Recalculate which attributes we have to display
2170 olddisplayattrs = self.displayattrs
2171 self.calcdisplayattrs()
2172 # If there are new attributes, recreate the cache
2173 if self.displayattrs != olddisplayattrs:
2174 endy = min(self.datastarty+self.mainsizey, len(self.items))
2175 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
2176 elif self.datastarty<olddatastarty: # we did scroll up
2177 # drop rows from the end
2178 del self.displayrows[self.datastarty-olddatastarty:]
2179 # fetch new items
2180 for i in xrange(olddatastarty-1,
2181 self.datastarty-1, -1):
2182 try:
2183 row = self.getrow(i)
2184 except IndexError:
2185 # we didn't have enough objects to fill the screen
2186 break
2187 self.displayrows.insert(0, row)
2188 else: # we did scroll down
2189 # drop rows from the start
2190 del self.displayrows[:self.datastarty-olddatastarty]
2191 # fetch new items
2192 for i in xrange(olddatastarty+self.mainsizey,
2193 self.datastarty+self.mainsizey):
2194 try:
2195 row = self.getrow(i)
2196 except IndexError:
2197 # we didn't have enough objects to fill the screen
2198 break
2199 self.displayrows.append(row)
2200 self.calcwidths()
2201
2202 # Make sure that the cursor didn't leave the data area horizontally
2203 if x < 0:
2204 x = 0
2205 elif x >= self.datasizex:
2206 x = max(0, self.datasizex-1)
2207
2208 # Make sure that the cursor stays on screen horizontally
2209 if x < self.datastartx+scrollborderx:
2210 self.datastartx = max(0, x-scrollborderx)
2211 elif x >= self.datastartx+self.mainsizex-scrollborderx:
2212 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
2213 self.datasizex-self.mainsizex))
2214
2215 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
2216 self.browser.beep()
2217 else:
2218 self.curx = x
2219 self.cury = y
2220 self.calcdisplayattr()
2221
2222 def sort(self, key, reverse=False):
2223 """
2224 Sort the currently list of items using the key function ``key``. If
2225 ``reverse`` is true the sort order is reversed.
2226 """
2227 curitem = self.items[self.cury] # Remember where the cursor is now
2228
2229 # Sort items
2230 def realkey(item):
2231 return key(item.item)
2232 self.items = deque(sorted(self.items, key=realkey, reverse=reverse))
2233
2234 # Find out where the object under the cursor went
2235 cury = self.cury
2236 for (i, item) in enumerate(self.items):
2237 if item is curitem:
2238 cury = i
2239 break
2240
2241 self.moveto(self.curx, cury, refresh=True)
2242
2243
2244 class ibrowse(Display):
2245 # Show this many lines from the previous screen when paging horizontally
2246 pageoverlapx = 1
2247
2248 # Show this many lines from the previous screen when paging vertically
2249 pageoverlapy = 1
2250
2251 # Start scrolling when the cursor is less than this number of columns
2252 # away from the left or right screen edge
2253 scrollborderx = 10
2254
2255 # Start scrolling when the cursor is less than this number of lines
2256 # away from the top or bottom screen edge
2257 scrollbordery = 5
2258
2259 # Accelerate by this factor when scrolling horizontally
2260 acceleratex = 1.05
2261
2262 # Accelerate by this factor when scrolling vertically
2263 acceleratey = 1.05
2264
2265 # The maximum horizontal scroll speed
2266 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
2267 maxspeedx = 0.5
2268
2269 # The maximum vertical scroll speed
2270 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
2271 maxspeedy = 0.5
2272
2273 # The maximum number of header lines for browser level
2274 # if the nesting is deeper, only the innermost levels are displayed
2275 maxheaders = 5
2276
2277 # The approximate maximum length of a column entry
2278 maxattrlength = 200
2279
2280 # Styles for various parts of the GUI
2281 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
2282 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
2283 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
2284 style_colheader = astyle.Style.fromstr("blue:white:reverse")
2285 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
2286 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
2287 style_number = astyle.Style.fromstr("blue:white:reverse")
2288 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
2289 style_sep = astyle.Style.fromstr("blue:black")
2290 style_data = astyle.Style.fromstr("white:black")
2291 style_datapad = astyle.Style.fromstr("blue:black:bold")
2292 style_footer = astyle.Style.fromstr("black:white")
2293 style_report = astyle.Style.fromstr("white:black")
2294
2295 # Column separator in header
2296 headersepchar = "|"
2297
2298 # Character for padding data cell entries
2299 datapadchar = "."
2300
2301 # Column separator in data area
2302 datasepchar = "|"
2303
2304 # Character to use for "empty" cell (i.e. for non-existing attributes)
2305 nodatachar = "-"
2306
2307 # Prompts for modes that require keyboard input
2308 prompts = {
2309 "goto": "goto object #: ",
2310 "find": "find expression: ",
2311 "findbackwards": "find backwards expression: "
2312 }
2313
2314 # Maps curses key codes to "function" names
2315 keymap = {
2316 ord("q"): "quit",
2317 curses.KEY_UP: "up",
2318 curses.KEY_DOWN: "down",
2319 curses.KEY_PPAGE: "pageup",
2320 curses.KEY_NPAGE: "pagedown",
2321 curses.KEY_LEFT: "left",
2322 curses.KEY_RIGHT: "right",
2323 curses.KEY_HOME: "home",
2324 curses.KEY_END: "end",
2325 ord("<"): "prevattr",
2326 0x1b: "prevattr", # SHIFT-TAB
2327 ord(">"): "nextattr",
2328 ord("\t"):"nextattr", # TAB
2329 ord("p"): "pick",
2330 ord("P"): "pickattr",
2331 ord("C"): "pickallattrs",
2332 ord("m"): "pickmarked",
2333 ord("M"): "pickmarkedattr",
2334 ord("\n"): "enterdefault",
2335 # FIXME: What's happening here?
2336 8: "leave",
2337 127: "leave",
2338 curses.KEY_BACKSPACE: "leave",
2339 ord("x"): "leave",
2340 ord("h"): "help",
2341 ord("e"): "enter",
2342 ord("E"): "enterattr",
2343 ord("d"): "detail",
2344 ord("D"): "detailattr",
2345 ord(" "): "tooglemark",
2346 ord("r"): "markrange",
2347 ord("v"): "sortattrasc",
2348 ord("V"): "sortattrdesc",
2349 ord("g"): "goto",
2350 ord("f"): "find",
2351 ord("b"): "findbackwards",
2352 }
2353
2354 def __init__(self, *attrs):
2355 """
2356 Create a new browser. If ``attrs`` is not empty, it is the list
2357 of attributes that will be displayed in the browser, otherwise
2358 these will be determined by the objects on screen.
2359 """
2360 self.attrs = attrs
2361
2362 # Stack of browser levels
2363 self.levels = []
2364 # how many colums to scroll (Changes when accelerating)
2365 self.stepx = 1.
2366
2367 # how many rows to scroll (Changes when accelerating)
2368 self.stepy = 1.
2369
2370 # Beep on the edges of the data area? (Will be set to ``False``
2371 # once the cursor hits the edge of the screen, so we don't get
2372 # multiple beeps).
2373 self._dobeep = True
2374
2375 # Cache for registered ``curses`` colors and styles.
2376 self._styles = {}
2377 self._colors = {}
2378 self._maxcolor = 1
2379
2380 # How many header lines do we want to paint (the numbers of levels
2381 # we have, but with an upper bound)
2382 self._headerlines = 1
2383
2384 # Index of first header line
2385 self._firstheaderline = 0
2386
2387 # curses window
2388 self.scr = None
2389 # report in the footer line (error, executed command etc.)
2390 self._report = None
2391
2392 # value to be returned to the caller (set by commands)
2393 self.returnvalue = None
2394
2395 # The mode the browser is in
2396 # e.g. normal browsing or entering an argument for a command
2397 self.mode = "default"
2398
2399 # The partially entered row number for the goto command
2400 self.goto = ""
2401
2402 def nextstepx(self, step):
2403 """
2404 Accelerate horizontally.
2405 """
2406 return max(1., min(step*self.acceleratex,
2407 self.maxspeedx*self.levels[-1].mainsizex))
2408
2409 def nextstepy(self, step):
2410 """
2411 Accelerate vertically.
2412 """
2413 return max(1., min(step*self.acceleratey,
2414 self.maxspeedy*self.levels[-1].mainsizey))
2415
2416 def getstyle(self, style):
2417 """
2418 Register the ``style`` with ``curses`` or get it from the cache,
2419 if it has been registered before.
2420 """
2421 try:
2422 return self._styles[style.fg, style.bg, style.attrs]
2423 except KeyError:
2424 attrs = 0
2425 for b in astyle.A2CURSES:
2426 if style.attrs & b:
2427 attrs |= astyle.A2CURSES[b]
2428 try:
2429 color = self._colors[style.fg, style.bg]
2430 except KeyError:
2431 curses.init_pair(
2432 self._maxcolor,
2433 astyle.COLOR2CURSES[style.fg],
2434 astyle.COLOR2CURSES[style.bg]
2435 )
2436 color = curses.color_pair(self._maxcolor)
2437 self._colors[style.fg, style.bg] = color
2438 self._maxcolor += 1
2439 c = color | attrs
2440 self._styles[style.fg, style.bg, style.attrs] = c
2441 return c
2442
2443 def addstr(self, y, x, begx, endx, text, style):
2444 """
2445 A version of ``curses.addstr()`` that can handle ``x`` coordinates
2446 that are outside the screen.
2447 """
2448 text2 = text[max(0, begx-x):max(0, endx-x)]
2449 if text2:
2450 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
2451 return len(text)
2452
2453 def addchr(self, y, x, begx, endx, c, l, style):
2454 x0 = max(x, begx)
2455 x1 = min(x+l, endx)
2456 if x1>x0:
2457 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
2458 return l
2459
2460 def _calcheaderlines(self, levels):
2461 # Calculate how many headerlines do we have to display, if we have
2462 # ``levels`` browser levels
2463 if levels is None:
2464 levels = len(self.levels)
2465 self._headerlines = min(self.maxheaders, levels)
2466 self._firstheaderline = levels-self._headerlines
2467
2468 def getstylehere(self, style):
2469 """
2470 Return a style for displaying the original style ``style``
2471 in the row the cursor is on.
2472 """
2473 return astyle.Style(style.fg, style.bg, style.attrs | astyle.A_BOLD)
2474
2475 def report(self, msg):
2476 """
2477 Store the message ``msg`` for display below the footer line. This
2478 will be displayed as soon as the screen is redrawn.
2479 """
2480 self._report = msg
2481
2482 def enter(self, item, mode, *attrs):
2483 """
2484 Enter the object ``item`` in the mode ``mode``. If ``attrs`` is
2485 specified, it will be used as a fixed list of attributes to display.
2486 """
2487 try:
2488 iterator = xiter(item, mode)
2489 except (KeyboardInterrupt, SystemExit):
2490 raise
2491 except Exception, exc:
2492 curses.beep()
2493 self.report(exc)
2494 else:
2495 self._calcheaderlines(len(self.levels)+1)
2496 level = _BrowserLevel(
2497 self,
2498 item,
2499 iterator,
2500 self.scrsizey-1-self._headerlines-2,
2501 *attrs
2502 )
2503 self.levels.append(level)
2504
2505 def startkeyboardinput(self, mode):
2506 """
2507 Enter mode ``mode``, which requires keyboard input.
2508 """
2509 self.mode = mode
2510 self.keyboardinput = ""
2511 self.cursorpos = 0
2512
2513 def executekeyboardinput(self, mode):
2514 exe = getattr(self, "exe_%s" % mode, None)
2515 if exe is not None:
2516 exe()
2517 self.mode = "default"
2518
2519 def keylabel(self, keycode):
2520 """
2521 Return a pretty name for the ``curses`` key ``keycode`` (used in the
2522 help screen and in reports about unassigned keys).
2523 """
2524 if keycode <= 0xff:
2525 specialsnames = {
2526 ord("\n"): "RETURN",
2527 ord(" "): "SPACE",
2528 ord("\t"): "TAB",
2529 ord("\x7f"): "DELETE",
2530 ord("\x08"): "BACKSPACE",
2531 }
2532 if keycode in specialsnames:
2533 return specialsnames[keycode]
2534 return repr(chr(keycode))
2535 for name in dir(curses):
2536 if name.startswith("KEY_") and getattr(curses, name) == keycode:
2537 return name
2538 return str(keycode)
2539
2540 def beep(self, force=False):
2541 if force or self._dobeep:
2542 curses.beep()
2543 # don't beep again (as long as the same key is pressed)
2544 self._dobeep = False
2545
2546 def cmd_quit(self):
2547 self.returnvalue = None
2548 return True
2549
2550 def cmd_up(self):
2551 level = self.levels[-1]
2552 self.report("up")
2553 level.moveto(level.curx, level.cury-self.stepy)
2554
2555 def cmd_down(self):
2556 level = self.levels[-1]
2557 self.report("down")
2558 level.moveto(level.curx, level.cury+self.stepy)
2559
2560 def cmd_pageup(self):
2561 level = self.levels[-1]
2562 self.report("page up")
2563 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
2564
2565 def cmd_pagedown(self):
2566 level = self.levels[-1]
2567 self.report("page down")
2568 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
2569
2570 def cmd_left(self):
2571 level = self.levels[-1]
2572 self.report("left")
2573 level.moveto(level.curx-self.stepx, level.cury)
2574
2575 def cmd_right(self):
2576 level = self.levels[-1]
2577 self.report("right")
2578 level.moveto(level.curx+self.stepx, level.cury)
2579
2580 def cmd_home(self):
2581 level = self.levels[-1]
2582 self.report("home")
2583 level.moveto(0, level.cury)
2584
2585 def cmd_end(self):
2586 level = self.levels[-1]
2587 self.report("end")
2588 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
2589
2590 def cmd_prevattr(self):
2591 level = self.levels[-1]
2592 if level.displayattr[0] is None or level.displayattr[0] == 0:
2593 self.beep()
2594 else:
2595 self.report("prevattr")
2596 pos = 0
2597 for (i, attrname) in enumerate(level.displayattrs):
2598 if i == level.displayattr[0]-1:
2599 break
2600 pos += level.colwidths[attrname] + 1
2601 level.moveto(pos, level.cury)
2602
2603 def cmd_nextattr(self):
2604 level = self.levels[-1]
2605 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
2606 self.beep()
2607 else:
2608 self.report("nextattr")
2609 pos = 0
2610 for (i, attrname) in enumerate(level.displayattrs):
2611 if i == level.displayattr[0]+1:
2612 break
2613 pos += level.colwidths[attrname] + 1
2614 level.moveto(pos, level.cury)
2615
2616 def cmd_pick(self):
2617 level = self.levels[-1]
2618 self.returnvalue = level.items[level.cury].item
2619 return True
2620
2621 def cmd_pickattr(self):
2622 level = self.levels[-1]
2623 attrname = level.displayattr[1]
2624 if attrname is _default:
2625 curses.beep()
2626 self.report(AttributeError(_attrname(attrname)))
2627 return
2628 attr = _getattr(level.items[level.cury].item, attrname)
2629 if attr is _default:
2630 curses.beep()
2631 self.report(AttributeError(_attrname(attrname)))
2632 else:
2633 self.returnvalue = attr
2634 return True
2635
2636 def cmd_pickallattrs(self):
2637 level = self.levels[-1]
2638 attrname = level.displayattr[1]
2639 if attrname is _default:
2640 curses.beep()
2641 self.report(AttributeError(_attrname(attrname)))
2642 return
2643 result = []
2644 for cache in level.items:
2645 attr = _getattr(cache.item, attrname)
2646 if attr is not _default:
2647 result.append(attr)
2648 self.returnvalue = result
2649 return True
2650
2651 def cmd_pickmarked(self):
2652 level = self.levels[-1]
2653 self.returnvalue = [cache.item for cache in level.items if cache.marked]
2654 return True
2655
2656 def cmd_pickmarkedattr(self):
2657 level = self.levels[-1]
2658 attrname = level.displayattr[1]
2659 if attrname is _default:
2660 curses.beep()
2661 self.report(AttributeError(_attrname(attrname)))
2662 return
2663 result = []
2664 for cache in level.items:
2665 if cache.marked:
2666 attr = _getattr(cache.item, attrname)
2667 if attr is not _default:
2668 result.append(attr)
2669 self.returnvalue = result
2670 return True
2671
2672 def cmd_markrange(self):
2673 level = self.levels[-1]
2674 self.report("markrange")
2675 start = None
2676 if level.items:
2677 for i in xrange(level.cury, -1, -1):
2678 if level.items[i].marked:
2679 start = i
2680 break
2681 if start is None:
2682 self.report(CommandError("no mark before cursor"))
2683 curses.beep()
2684 else:
2685 for i in xrange(start, level.cury+1):
2686 cache = level.items[i]
2687 if not cache.marked:
2688 cache.marked = True
2689 level.marked += 1
2690
2691 def cmd_enterdefault(self):
2692 level = self.levels[-1]
2693 try:
2694 item = level.items[level.cury].item
2695 except IndexError:
2696 self.report(CommandError("No object"))
2697 curses.beep()
2698 else:
2699 self.report("entering object (default mode)...")
2700 self.enter(item, "default")
2701
2702 def cmd_leave(self):
2703 self.report("leave")
2704 if len(self.levels) > 1:
2705 self._calcheaderlines(len(self.levels)-1)
2706 self.levels.pop(-1)
2707 else:
2708 self.report(CommandError("This is the last level"))
2709 curses.beep()
2710
2711 def cmd_enter(self):
2712 level = self.levels[-1]
2713 try:
2714 item = level.items[level.cury].item
2715 except IndexError:
2716 self.report(CommandError("No object"))
2717 curses.beep()
2718 else:
2719 self.report("entering object...")
2720 self.enter(item, None)
2721
2722 def cmd_enterattr(self):
2723 level = self.levels[-1]
2724 attrname = level.displayattr[1]
2725 if attrname is _default:
2726 curses.beep()
2727 self.report(AttributeError(_attrname(attrname)))
2728 return
2729 try:
2730 item = level.items[level.cury].item
2731 except IndexError:
2732 self.report(CommandError("No object"))
2733 curses.beep()
2734 else:
2735 attr = _getattr(item, attrname)
2736 if attr is _default:
2737 self.report(AttributeError(_attrname(attrname)))
2738 else:
2739 self.report("entering object attribute %s..." % _attrname(attrname))
2740 self.enter(attr, None)
2741
2742 def cmd_detail(self):
2743 level = self.levels[-1]
2744 try:
2745 item = level.items[level.cury].item
2746 except IndexError:
2747 self.report(CommandError("No object"))
2748 curses.beep()
2749 else:
2750 self.report("entering detail view for object...")
2751 self.enter(item, "detail")
2752
2753 def cmd_detailattr(self):
2754 level = self.levels[-1]
2755 attrname = level.displayattr[1]
2756 if attrname is _default:
2757 curses.beep()
2758 self.report(AttributeError(_attrname(attrname)))
2759 return
2760 try:
2761 item = level.items[level.cury].item
2762 except IndexError:
2763 self.report(CommandError("No object"))
2764 curses.beep()
2765 else:
2766 attr = _getattr(item, attrname)
2767 if attr is _default:
2768 self.report(AttributeError(_attrname(attrname)))
2769 else:
2770 self.report("entering detail view for attribute...")
2771 self.enter(attr, "detail")
2772
2773 def cmd_tooglemark(self):
2774 level = self.levels[-1]
2775 self.report("toggle mark")
2776 try:
2777 item = level.items[level.cury]
2778 except IndexError: # no items?
2779 pass
2780 else:
2781 if item.marked:
2782 item.marked = False
2783 level.marked -= 1
2784 else:
2785 item.marked = True
2786 level.marked += 1
2787
2788 def cmd_sortattrasc(self):
2789 level = self.levels[-1]
2790 attrname = level.displayattr[1]
2791 if attrname is _default:
2792 curses.beep()
2793 self.report(AttributeError(_attrname(attrname)))
2794 return
2795 self.report("sort by %s (ascending)" % _attrname(attrname))
2796 def key(item):
2797 try:
2798 return _getattr(item, attrname, None)
2799 except (KeyboardInterrupt, SystemExit):
2800 raise
2801 except Exception:
2802 return None
2803 level.sort(key)
2804
2805 def cmd_sortattrdesc(self):
2806 level = self.levels[-1]
2807 attrname = level.displayattr[1]
2808 if attrname is _default:
2809 curses.beep()
2810 self.report(AttributeError(_attrname(attrname)))
2811 return
2812 self.report("sort by %s (descending)" % _attrname(attrname))
2813 def key(item):
2814 try:
2815 return _getattr(item, attrname, None)
2816 except (KeyboardInterrupt, SystemExit):
2817 raise
2818 except Exception:
2819 return None
2820 level.sort(key, reverse=True)
2821
2822 def cmd_goto(self):
2823 self.startkeyboardinput("goto")
2824
2825 def exe_goto(self):
2826 level = self.levels[-1]
2827 if self.keyboardinput:
2828 level.moveto(level.curx, int(self.keyboardinput))
2829
2830 def cmd_find(self):
2831 self.startkeyboardinput("find")
2832
2833 def exe_find(self):
2834 level = self.levels[-1]
2835 if self.keyboardinput:
2836 while True:
2837 cury = level.cury
2838 level.moveto(level.curx, cury+1)
2839 if cury == level.cury:
2840 curses.beep()
2841 break
2842 item = level.items[level.cury].item
2843 try:
2844 if eval(self.keyboardinput, globals(), _AttrNamespace(item)):
2845 break
2846 except (KeyboardInterrupt, SystemExit):
2847 raise
2848 except Exception, exc:
2849 self.report(exc)
2850 curses.beep()
2851 break # break on error
2852
2853 def cmd_findbackwards(self):
2854 self.startkeyboardinput("findbackwards")
2855
2856 def exe_findbackwards(self):
2857 level = self.levels[-1]
2858 if self.keyboardinput:
2859 while level.cury:
2860 level.moveto(level.curx, level.cury-1)
2861 item = level.items[level.cury].item
2862 try:
2863 if eval(self.keyboardinput, globals(), _AttrNamespace(item)):
2864 break
2865 except (KeyboardInterrupt, SystemExit):
2866 raise
2867 except Exception, exc:
2868 self.report(exc)
2869 curses.beep()
2870 break # break on error
2871 else:
2872 curses.beep()
2873
2874 def cmd_help(self):
2875 """
2876 The help command
2877 """
2878 for level in self.levels:
2879 if isinstance(level.input, _BrowserHelp):
2880 curses.beep()
2881 self.report(CommandError("help already active"))
2882 return
2883
2884 self.enter(_BrowserHelp(self), "default")
2885
2886 def _dodisplay(self, scr):
2887 """
2888 This method is the workhorse of the browser. It handles screen
2889 drawing and the keyboard.
2890 """
2891 self.scr = scr
2892 curses.halfdelay(1)
2893 footery = 2
2894
2895 keys = []
2896 for (key, cmd) in self.keymap.iteritems():
2897 if cmd == "quit":
2898 keys.append("%s=%s" % (self.keylabel(key), cmd))
2899 for (key, cmd) in self.keymap.iteritems():
2900 if cmd == "help":
2901 keys.append("%s=%s" % (self.keylabel(key), cmd))
2902 helpmsg = " | %s" % " ".join(keys)
2903
2904 scr.clear()
2905 msg = "Fetching first batch of objects..."
2906 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
2907 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
2908 scr.refresh()
2909
2910 lastc = -1
2911
2912 self.levels = []
2913 # enter the first level
2914 self.enter(self.input, xiter(self.input, "default"), *self.attrs)
2915
2916 self._calcheaderlines(None)
2917
2918 while True:
2919 level = self.levels[-1]
2920 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
2921 level.mainsizey = self.scrsizey-1-self._headerlines-footery
2922
2923 # Paint object header
2924 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
2925 lv = self.levels[i]
2926 posx = 0
2927 posy = i-self._firstheaderline
2928 endx = self.scrsizex
2929 if i: # not the first level
2930 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
2931 if not self.levels[i-1].exhausted:
2932 msg += "+"
2933 msg += ") "
2934 endx -= len(msg)+1
2935 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
2936 for (style, text) in lv.header:
2937 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
2938 if posx >= endx:
2939 break
2940 if i:
2941 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
2942 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
2943
2944 if not level.items:
2945 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
2946 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", style_error)
2947 scr.clrtobot()
2948 else:
2949 # Paint column headers
2950 scr.move(self._headerlines, 0)
2951 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
2952 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
2953 begx = level.numbersizex+3
2954 posx = begx-level.datastartx
2955 for attrname in level.displayattrs:
2956 strattrname = _attrname(attrname)
2957 cwidth = level.colwidths[attrname]
2958 header = strattrname.ljust(cwidth)
2959 if attrname == level.displayattr[1]:
2960 style = self.style_colheaderhere
2961 else:
2962 style = self.style_colheader
2963 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
2964 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
2965 if posx >= self.scrsizex:
2966 break
2967 else:
2968 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
2969
2970 # Paint rows
2971 posy = self._headerlines+1+level.datastarty
2972 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
2973 cache = level.items[i]
2974 if i == level.cury:
2975 style = self.style_numberhere
2976 else:
2977 style = self.style_number
2978
2979 posy = self._headerlines+1+i-level.datastarty
2980 posx = begx-level.datastartx
2981
2982 scr.move(posy, 0)
2983 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
2984 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
2985
2986 for attrname in level.displayattrs:
2987 cwidth = level.colwidths[attrname]
2988 try:
2989 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
2990 except KeyError:
2991 align = 2
2992 style = style_nodata
2993 padstyle = self.style_datapad
2994 sepstyle = self.style_sep
2995 if i == level.cury:
2996 padstyle = self.getstylehere(padstyle)
2997 sepstyle = self.getstylehere(sepstyle)
2998 if align == 2:
2999 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
3000 else:
3001 if align == 1:
3002 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
3003 elif align == 0:
3004 pad1 = (cwidth-length)//2
3005 pad2 = cwidth-length-len(pad1)
3006 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
3007 for (style, text) in parts:
3008 if i == level.cury:
3009 style = self.getstylehere(style)
3010 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
3011 if posx >= self.scrsizex:
3012 break
3013 if align == -1:
3014 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
3015 elif align == 0:
3016 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
3017 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
3018 else:
3019 scr.clrtoeol()
3020
3021 # Add blank row headers for the rest of the screen
3022 for posy in xrange(posy+1, self.scrsizey-2):
3023 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
3024 scr.clrtoeol()
3025
3026 posy = self.scrsizey-footery
3027 # Display footer
3028 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
3029
3030 if level.exhausted:
3031 flag = ""
3032 else:
3033 flag = "+"
3034
3035 endx = self.scrsizex-len(helpmsg)-1
3036 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
3037
3038 posx = 0
3039 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
3040 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
3041 try:
3042 item = level.items[level.cury].item
3043 except IndexError: # empty
3044 pass
3045 else:
3046 for (nostyle, text) in xrepr(item, "footer"):
3047 if not isinstance(nostyle, int):
3048 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
3049 if posx >= endx:
3050 break
3051
3052 attrstyle = [(astyle.style_default, "no attribute")]
3053 attrname = level.displayattr[1]
3054 if attrname is not _default and attrname is not None:
3055 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
3056 posx += self.addstr(posy, posx, 0, endx, _attrname(attrname), self.style_footer)
3057 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
3058 try:
3059 attr = _getattr(item, attrname)
3060 except (SystemExit, KeyboardInterrupt):
3061 raise
3062 except Exception, exc:
3063 attr = exc
3064 if attr is not _default:
3065 attrstyle = xrepr(attr, "footer")
3066 for (nostyle, text) in attrstyle:
3067 if not isinstance(nostyle, int):
3068 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
3069 if posx >= endx:
3070 break
3071
3072 try:
3073 # Display input prompt
3074 if self.mode in self.prompts:
3075 scr.addstr(self.scrsizey-1, 0,
3076 self.prompts[self.mode] + self.keyboardinput,
3077 self.getstyle(style_default))
3078 # Display report
3079 else:
3080 if self._report is not None:
3081 if isinstance(self._report, Exception):
3082 style = self.getstyle(style_error)
3083 if self._report.__class__.__module__ == "exceptions":
3084 msg = "%s: %s" % \
3085 (self._report.__class__.__name__, self._report)
3086 else:
3087 msg = "%s.%s: %s" % \
3088 (self._report.__class__.__module__,
3089 self._report.__class__.__name__, self._report)
3090 else:
3091 style = self.getstyle(self.style_report)
3092 msg = self._report
3093 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
3094 self._report = None
3095 else:
3096 scr.move(self.scrsizey-1, 0)
3097 except curses.error:
3098 # Protect against error from writing to the last line
3099 pass
3100 scr.clrtoeol()
3101
3102 # Position cursor
3103 if self.mode in self.prompts:
3104 scr.move(self.scrsizey-1, len(self.prompts[self.mode])+self.cursorpos)
3105 else:
3106 scr.move(
3107 1+self._headerlines+level.cury-level.datastarty,
3108 level.numbersizex+3+level.curx-level.datastartx
3109 )
3110 scr.refresh()
3111
3112 # Check keyboard
3113 while True:
3114 c = scr.getch()
3115 if self.mode in self.prompts:
3116 if c in (8, 127, curses.KEY_BACKSPACE):
3117 if self.cursorpos:
3118 self.keyboardinput = self.keyboardinput[:self.cursorpos-1] + self.keyboardinput[self.cursorpos:]
3119 self.cursorpos -= 1
3120 break
3121 else:
3122 curses.beep()
3123 elif c == curses.KEY_LEFT:
3124 if self.cursorpos:
3125 self.cursorpos -= 1
3126 break
3127 else:
3128 curses.beep()
3129 elif c == curses.KEY_RIGHT:
3130 if self.cursorpos < len(self.keyboardinput):
3131 self.cursorpos += 1
3132 break
3133 else:
3134 curses.beep()
3135 elif c in (curses.KEY_UP, curses.KEY_DOWN): # cancel
3136 self.mode = "default"
3137 break
3138 elif c == ord("\n"):
3139 self.executekeyboardinput(self.mode)
3140 break
3141 elif c != -1:
3142 try:
3143 c = chr(c)
3144 except ValueError:
3145 curses.beep()
3146 else:
3147 if (self.mode == "goto" and not "0" <= c <= "9"):
3148 curses.beep()
3149 else:
3150 self.keyboardinput = self.keyboardinput[:self.cursorpos] + c + self.keyboardinput[self.cursorpos:]
3151 self.cursorpos += 1
3152 break # Redisplay
3153 else:
3154 # if no key is pressed slow down and beep again
3155 if c == -1:
3156 self.stepx = 1.
3157 self.stepy = 1.
3158 self._dobeep = True
3159 else:
3160 # if a different key was pressed slow down and beep too
3161 if c != lastc:
3162 lastc = c
3163 self.stepx = 1.
3164 self.stepy = 1.
3165 self._dobeep = True
3166 cmdname = self.keymap.get(c, None)
3167 if cmdname is None:
3168 self.report(
3169 UnassignedKeyError("Unassigned key %s" %
3170 self.keylabel(c)))
3171 else:
3172 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
3173 if cmdfunc is None:
3174 self.report(
3175 UnknownCommandError("Unknown command %r" %
3176 (cmdname,)))
3177 elif cmdfunc():
3178 returnvalue = self.returnvalue
3179 self.returnvalue = None
3180 return returnvalue
3181 self.stepx = self.nextstepx(self.stepx)
3182 self.stepy = self.nextstepy(self.stepy)
3183 curses.flushinp() # get rid of type ahead
3184 break # Redisplay
3185 self.scr = None
3186
3187 def display(self):
3188 return curses.wrapper(self._dodisplay)
3189
3190 defaultdisplay = ibrowse
3191 __all__.append("ibrowse")
3192 else:
3193 # No curses (probably Windows) => use ``idump`` as the default display.
1780 # No curses (probably Windows) => use ``idump`` as the default display.
3194 defaultdisplay = idump
1781 defaultdisplay = idump
1782 else:
1783 defaultdisplay = ibrowse
1784 __all__.append("ibrowse")
3195
1785
3196
1786
3197 # If we're running under IPython, install an IPython displayhook that
1787 # If we're running under IPython, install an IPython displayhook that
3198 # returns the object from Display.display(), else install a displayhook
1788 # returns the object from Display.display(), else install a displayhook
3199 # directly as sys.displayhook
1789 # directly as sys.displayhook
3200 try:
1790 try:
3201 from IPython import ipapi
1791 from IPython import ipapi
3202 api = ipapi.get()
1792 api = ipapi.get()
3203 except (ImportError, AttributeError):
1793 except (ImportError, AttributeError):
3204 api = None
1794 api = None
3205
1795
3206 if api is not None:
1796 if api is not None:
3207 def displayhook(self, obj):
1797 def displayhook(self, obj):
3208 if isinstance(obj, type) and issubclass(obj, Table):
1798 if isinstance(obj, type) and issubclass(obj, Table):
3209 obj = obj()
1799 obj = obj()
3210 if isinstance(obj, Table):
1800 if isinstance(obj, Table):
3211 obj = obj | defaultdisplay
1801 obj = obj | defaultdisplay
3212 if isinstance(obj, Display):
1802 if isinstance(obj, Display):
3213 return obj.display()
1803 return obj.display()
3214 else:
1804 else:
3215 raise ipapi.TryNext
1805 raise ipapi.TryNext
3216 api.set_hook("result_display", displayhook)
1806 api.set_hook("result_display", displayhook)
3217 else:
1807 else:
3218 def installdisplayhook():
1808 def installdisplayhook():
3219 _originalhook = sys.displayhook
1809 _originalhook = sys.displayhook
3220 def displayhook(obj):
1810 def displayhook(obj):
3221 if isinstance(obj, type) and issubclass(obj, Table):
1811 if isinstance(obj, type) and issubclass(obj, Table):
3222 obj = obj()
1812 obj = obj()
3223 if isinstance(obj, Table):
1813 if isinstance(obj, Table):
3224 obj = obj | defaultdisplay
1814 obj = obj | defaultdisplay
3225 if isinstance(obj, Display):
1815 if isinstance(obj, Display):
3226 return obj.display()
1816 return obj.display()
3227 else:
1817 else:
3228 _originalhook(obj)
1818 _originalhook(obj)
3229 sys.displayhook = displayhook
1819 sys.displayhook = displayhook
3230 installdisplayhook()
1820 installdisplayhook()
General Comments 0
You need to be logged in to leave comments. Login now