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