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