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