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