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