##// END OF EJS Templates
Walter's ipipe patch #8:...
vivainio -
Show More
@@ -1,3138 +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 911 abspath = repr(path._base(self.abspath()))
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 if isinstance(doc, basestring):
1785 doc = doc.strip()
1784 1786 self.doc = doc
1785 1787
1786 1788 def __xattrs__(self, mode):
1787 1789 return ("name", "type", "doc", "value")
1788 1790
1789 1791
1790 1792 _ibrowse_help = """
1791 1793 down
1792 1794 Move the cursor to the next line.
1793 1795
1794 1796 up
1795 1797 Move the cursor to the previous line.
1796 1798
1797 1799 pagedown
1798 1800 Move the cursor down one page (minus overlap).
1799 1801
1800 1802 pageup
1801 1803 Move the cursor up one page (minus overlap).
1802 1804
1803 1805 left
1804 1806 Move the cursor left.
1805 1807
1806 1808 right
1807 1809 Move the cursor right.
1808 1810
1809 1811 home
1810 1812 Move the cursor to the first column.
1811 1813
1812 1814 end
1813 1815 Move the cursor to the last column.
1814 1816
1815 1817 prevattr
1816 1818 Move the cursor one attribute column to the left.
1817 1819
1818 1820 nextattr
1819 1821 Move the cursor one attribute column to the right.
1820 1822
1821 1823 pick
1822 1824 'Pick' the object under the cursor (i.e. the row the cursor is on). This leaves
1823 1825 the browser and returns the picked object to the caller. (In IPython this object
1824 1826 will be available as the '_' variable.)
1825 1827
1826 1828 pickattr
1827 1829 'Pick' the attribute under the cursor (i.e. the row/column the cursor is on).
1828 1830
1829 1831 pickallattrs
1830 1832 Pick' the complete column under the cursor (i.e. the attribute under the cursor)
1831 1833 from all currently fetched objects. These attributes will be returned as a list.
1832 1834
1833 1835 tooglemark
1834 1836 Mark/unmark the object under the cursor. Marked objects have a '!' after the
1835 1837 row number).
1836 1838
1837 1839 pickmarked
1838 1840 'Pick' marked objects. Marked objects will be returned as a list.
1839 1841
1840 1842 pickmarkedattr
1841 1843 'Pick' the attribute under the cursor from all marked objects (This returns a
1842 1844 list).
1843 1845
1844 1846 enterdefault
1845 1847 Enter the object under the cursor. (what this mean depends on the object
1846 1848 itself (i.e. how it implements the '__xiter__' method). This opens a new browser
1847 1849 'level'.
1848 1850
1849 1851 enter
1850 1852 Enter the object under the cursor. If the object provides different enter modes
1851 1853 a menu of all modes will be presented; choice one and enter it (via the 'enter'
1852 1854 or 'enterdefault' command).
1853 1855
1854 1856 enterattr
1855 1857 Enter the attribute under the cursor.
1856 1858
1857 1859 leave
1858 1860 Leave the current browser level and go back to the previous one.
1859 1861
1860 1862 detail
1861 1863 Show a detail view of the object under the cursor. This shows the name, type,
1862 1864 doc string and value of the object attributes (and it might show more attributes
1863 1865 than in the list view, depending on the object).
1864 1866
1865 1867 detailattr
1866 1868 Show a detail view of the attribute under the cursor.
1867 1869
1868 1870 markrange
1869 1871 Mark all objects from the last marked object before the current cursor position
1870 1872 to the cursor position.
1871 1873
1872 1874 sortattrasc
1873 1875 Sort the objects (in ascending order) using the attribute under the cursor as
1874 1876 the sort key.
1875 1877
1876 1878 sortattrdesc
1877 1879 Sort the objects (in descending order) using the attribute under the cursor as
1878 1880 the sort key.
1879 1881
1882 goto
1883 Jump to a row. The row number can be entered at the bottom of the screen.
1884
1880 1885 help
1881 1886 This screen.
1882 1887 """
1883 1888
1884 1889
1885 1890 if curses is not None:
1886 1891 class UnassignedKeyError(Exception):
1887 1892 """
1888 1893 Exception that is used for reporting unassigned keys.
1889 1894 """
1890 1895
1891 1896
1892 1897 class UnknownCommandError(Exception):
1893 1898 """
1894 1899 Exception that is used for reporting unknown command (this should never
1895 1900 happen).
1896 1901 """
1897 1902
1898 1903
1899 1904 class CommandError(Exception):
1900 1905 """
1901 1906 Exception that is used for reporting that a command can't be executed.
1902 1907 """
1903 1908
1904 1909
1905 1910 class _BrowserCachedItem(object):
1906 1911 # This is used internally by ``ibrowse`` to store a item together with its
1907 1912 # marked status.
1908 1913 __slots__ = ("item", "marked")
1909 1914
1910 1915 def __init__(self, item):
1911 1916 self.item = item
1912 1917 self.marked = False
1913 1918
1914 1919
1915 1920 class _BrowserHelp(object):
1916 1921 # This is used internally by ``ibrowse`` for displaying the help screen.
1917 1922 def __init__(self, browser):
1918 1923 self.browser = browser
1919 1924
1920 1925 def __xrepr__(self, mode):
1926 yield (-1, True)
1921 1927 if mode == "header" or mode == "footer":
1922 return "ibrowse help screen"
1923 return repr(self)
1928 yield (style_default, "ibrowse help screen")
1929 else:
1930 yield (style_default, repr(self))
1924 1931
1925 1932 def __xiter__(self, mode):
1926 1933 # Get reverse key mapping
1927 1934 allkeys = {}
1928 1935 for (key, cmd) in self.browser.keymap.iteritems():
1929 1936 allkeys.setdefault(cmd, []).append(key)
1930 1937
1931 1938 fields = ("key", "command", "description")
1932 1939
1933 1940 for (i, command) in enumerate(_ibrowse_help.strip().split("\n\n")):
1934 1941 if i:
1935 1942 yield Fields(fields, key="", command="", description="")
1936 1943
1937 1944 (name, description) = command.split("\n", 1)
1938 1945 keys = allkeys.get(name, [])
1939 1946 lines = textwrap.wrap(description, 50)
1940 1947
1941 1948 for i in xrange(max(len(keys), len(lines))):
1942 1949 if i:
1943 1950 name = ""
1944 1951 try:
1945 1952 key = self.browser.keylabel(keys[i])
1946 1953 except IndexError:
1947 1954 key = ""
1948 1955 try:
1949 1956 line = lines[i]
1950 1957 except IndexError:
1951 1958 line = ""
1952 1959 yield Fields(fields, key=key, command=name, description=line)
1953 1960
1954 1961
1955 1962 class _BrowserLevel(object):
1956 1963 # This is used internally to store the state (iterator, fetch items,
1957 1964 # position of cursor and screen, etc.) of one browser level
1958 1965 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
1959 1966 # a stack.
1960 1967 def __init__(self, browser, input, iterator, mainsizey, *attrs):
1961 1968 self.browser = browser
1962 1969 self.input = input
1963 1970 self.header = [x for x in xrepr(input, "header") if not isinstance(x[0], int)]
1964 1971 # iterator for the input
1965 1972 self.iterator = iterator
1966 1973
1967 1974 # is the iterator exhausted?
1968 1975 self.exhausted = False
1969 1976
1970 1977 # attributes to be display (autodetected if empty)
1971 1978 self.attrs = attrs
1972 1979
1973 1980 # fetched items (+ marked flag)
1974 1981 self.items = deque()
1975 1982
1976 1983 # Number of marked objects
1977 1984 self.marked = 0
1978 1985
1979 1986 # Vertical cursor position
1980 1987 self.cury = 0
1981 1988
1982 1989 # Horizontal cursor position
1983 1990 self.curx = 0
1984 1991
1985 1992 # Index of first data column
1986 1993 self.datastartx = 0
1987 1994
1988 1995 # Index of first data line
1989 1996 self.datastarty = 0
1990 1997
1991 1998 # height of the data display area
1992 1999 self.mainsizey = mainsizey
1993 2000
1994 2001 # width of the data display area (changes when scrolling)
1995 2002 self.mainsizex = 0
1996 2003
1997 2004 # Size of row number (changes when scrolling)
1998 2005 self.numbersizex = 0
1999 2006
2000 2007 # Attribute names to display (in this order)
2001 2008 self.displayattrs = []
2002 2009
2003 2010 # index and name of attribute under the cursor
2004 2011 self.displayattr = (None, _default)
2005 2012
2006 2013 # Maps attribute names to column widths
2007 2014 self.colwidths = {}
2008 2015
2009 2016 self.fetch(mainsizey)
2010 2017 self.calcdisplayattrs()
2011 2018 # formatted attributes for the items on screen
2012 2019 # (i.e. self.items[self.datastarty:self.datastarty+self.mainsizey])
2013 2020 self.displayrows = [self.getrow(i) for i in xrange(len(self.items))]
2014 2021 self.calcwidths()
2015 2022 self.calcdisplayattr()
2016 2023
2017 2024 def fetch(self, count):
2018 2025 # Try to fill ``self.items`` with at least ``count`` objects.
2019 2026 have = len(self.items)
2020 2027 while not self.exhausted and have < count:
2021 2028 try:
2022 2029 item = self.iterator.next()
2023 2030 except StopIteration:
2024 2031 self.exhausted = True
2025 2032 break
2026 2033 else:
2027 2034 have += 1
2028 2035 self.items.append(_BrowserCachedItem(item))
2029 2036
2030 2037 def calcdisplayattrs(self):
2031 2038 # Calculate which attributes are available from the objects that are
2032 2039 # currently visible on screen (and store it in ``self.displayattrs``)
2033 2040 attrnames = set()
2034 2041 # If the browser object specifies a fixed list of attributes,
2035 2042 # simply use it.
2036 2043 if self.attrs:
2037 2044 self.displayattrs = self.attrs
2038 2045 else:
2039 2046 self.displayattrs = []
2040 2047 endy = min(self.datastarty+self.mainsizey, len(self.items))
2041 2048 for i in xrange(self.datastarty, endy):
2042 2049 for attrname in xattrs(self.items[i].item, "default"):
2043 2050 if attrname not in attrnames:
2044 2051 self.displayattrs.append(attrname)
2045 2052 attrnames.add(attrname)
2046 2053
2047 2054 def getrow(self, i):
2048 2055 # Return a dictinary with the attributes for the object
2049 2056 # ``self.items[i]``. Attribute names are taken from
2050 2057 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
2051 2058 # called before.
2052 2059 row = {}
2053 2060 item = self.items[i].item
2054 2061 for attrname in self.displayattrs:
2055 2062 try:
2056 2063 value = _getattr(item, attrname, _default)
2057 2064 except (KeyboardInterrupt, SystemExit):
2058 2065 raise
2059 2066 except Exception, exc:
2060 2067 value = exc
2061 2068 # only store attribute if it exists (or we got an exception)
2062 2069 if value is not _default:
2063 2070 parts = []
2064 2071 totallength = 0
2065 2072 align = None
2066 2073 full = False
2067 2074 # Collect parts until we have enough
2068 2075 for part in xrepr(value, "cell"):
2069 2076 # part gives (alignment, stop)
2070 2077 # instead of (style, text)
2071 2078 if isinstance(part[0], int):
2072 2079 # only consider the first occurence
2073 2080 if align is None:
2074 2081 align = part[0]
2075 2082 full = part[1]
2076 2083 else:
2077 2084 parts.append(part)
2078 2085 totallength += len(part[1])
2079 2086 if totallength >= self.browser.maxattrlength and not full:
2080 2087 parts.append((style_ellisis, "..."))
2081 2088 totallength += 3
2082 2089 break
2083 2090 # remember alignment, length and colored parts
2084 2091 row[attrname] = (align, totallength, parts)
2085 2092 return row
2086 2093
2087 2094 def calcwidths(self):
2088 2095 # Recalculate the displayed fields and their width.
2089 2096 # ``calcdisplayattrs()'' must have been called and the cache
2090 2097 # for attributes of the objects on screen (``self.displayrows``)
2091 2098 # must have been filled. This returns a dictionary mapping
2092 2099 # colmn names to width.
2093 2100 self.colwidths = {}
2094 2101 for row in self.displayrows:
2095 2102 for attrname in self.displayattrs:
2096 2103 try:
2097 2104 length = row[attrname][1]
2098 2105 except KeyError:
2099 2106 length = 0
2100 2107 # always add attribute to colwidths, even if it doesn't exist
2101 2108 if attrname not in self.colwidths:
2102 2109 self.colwidths[attrname] = len(_attrname(attrname))
2103 2110 newwidth = max(self.colwidths[attrname], length)
2104 2111 self.colwidths[attrname] = newwidth
2105 2112
2106 2113 # How many characters do we need to paint the item number?
2107 2114 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
2108 2115 # How must space have we got to display data?
2109 2116 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
2110 2117 # width of all columns
2111 2118 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
2112 2119
2113 2120 def calcdisplayattr(self):
2114 2121 # Find out on which attribute the cursor is on and store this
2115 2122 # information in ``self.displayattr``.
2116 2123 pos = 0
2117 2124 for (i, attrname) in enumerate(self.displayattrs):
2118 2125 if pos+self.colwidths[attrname] >= self.curx:
2119 2126 self.displayattr = (i, attrname)
2120 2127 break
2121 2128 pos += self.colwidths[attrname]+1
2122 2129 else:
2123 2130 self.displayattr = (None, _default)
2124 2131
2125 2132 def moveto(self, x, y, refresh=False):
2126 2133 # Move the cursor to the position ``(x,y)`` (in data coordinates,
2127 2134 # not in screen coordinates). If ``refresh`` is true, all cached
2128 2135 # values will be recalculated (e.g. because the list has been
2129 2136 # resorted, so screen positions etc. are no longer valid).
2130 2137 olddatastarty = self.datastarty
2131 2138 oldx = self.curx
2132 2139 oldy = self.cury
2133 2140 x = int(x+0.5)
2134 2141 y = int(y+0.5)
2135 2142 newx = x # remember where we wanted to move
2136 2143 newy = y # remember where we wanted to move
2137 2144
2138 2145 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
2139 2146 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
2140 2147
2141 2148 # Make sure that the cursor didn't leave the main area vertically
2142 2149 if y < 0:
2143 2150 y = 0
2144 2151 self.fetch(y+scrollbordery+1) # try to get more items
2145 2152 if y >= len(self.items):
2146 2153 y = max(0, len(self.items)-1)
2147 2154
2148 2155 # Make sure that the cursor stays on screen vertically
2149 2156 if y < self.datastarty+scrollbordery:
2150 2157 self.datastarty = max(0, y-scrollbordery)
2151 2158 elif y >= self.datastarty+self.mainsizey-scrollbordery:
2152 2159 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
2153 2160 len(self.items)-self.mainsizey))
2154 2161
2155 2162 if refresh: # Do we need to refresh the complete display?
2156 2163 self.calcdisplayattrs()
2157 2164 endy = min(self.datastarty+self.mainsizey, len(self.items))
2158 2165 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
2159 2166 self.calcwidths()
2160 2167 # Did we scroll vertically => update displayrows
2161 2168 # and various other attributes
2162 2169 elif self.datastarty != olddatastarty:
2163 2170 # Recalculate which attributes we have to display
2164 2171 olddisplayattrs = self.displayattrs
2165 2172 self.calcdisplayattrs()
2166 2173 # If there are new attributes, recreate the cache
2167 2174 if self.displayattrs != olddisplayattrs:
2168 2175 endy = min(self.datastarty+self.mainsizey, len(self.items))
2169 2176 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
2170 2177 elif self.datastarty<olddatastarty: # we did scroll up
2171 2178 # drop rows from the end
2172 2179 del self.displayrows[self.datastarty-olddatastarty:]
2173 2180 # fetch new items
2174 2181 for i in xrange(olddatastarty-1,
2175 2182 self.datastarty-1, -1):
2176 2183 try:
2177 2184 row = self.getrow(i)
2178 2185 except IndexError:
2179 2186 # we didn't have enough objects to fill the screen
2180 2187 break
2181 2188 self.displayrows.insert(0, row)
2182 2189 else: # we did scroll down
2183 2190 # drop rows from the start
2184 2191 del self.displayrows[:self.datastarty-olddatastarty]
2185 2192 # fetch new items
2186 2193 for i in xrange(olddatastarty+self.mainsizey,
2187 2194 self.datastarty+self.mainsizey):
2188 2195 try:
2189 2196 row = self.getrow(i)
2190 2197 except IndexError:
2191 2198 # we didn't have enough objects to fill the screen
2192 2199 break
2193 2200 self.displayrows.append(row)
2194 2201 self.calcwidths()
2195 2202
2196 2203 # Make sure that the cursor didn't leave the data area horizontally
2197 2204 if x < 0:
2198 2205 x = 0
2199 2206 elif x >= self.datasizex:
2200 2207 x = max(0, self.datasizex-1)
2201 2208
2202 2209 # Make sure that the cursor stays on screen horizontally
2203 2210 if x < self.datastartx+scrollborderx:
2204 2211 self.datastartx = max(0, x-scrollborderx)
2205 2212 elif x >= self.datastartx+self.mainsizex-scrollborderx:
2206 2213 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
2207 2214 self.datasizex-self.mainsizex))
2208 2215
2209 2216 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
2210 2217 self.browser.beep()
2211 2218 else:
2212 2219 self.curx = x
2213 2220 self.cury = y
2214 2221 self.calcdisplayattr()
2215 2222
2216 2223 def sort(self, key, reverse=False):
2217 2224 """
2218 2225 Sort the currently list of items using the key function ``key``. If
2219 2226 ``reverse`` is true the sort order is reversed.
2220 2227 """
2221 2228 curitem = self.items[self.cury] # Remember where the cursor is now
2222 2229
2223 2230 # Sort items
2224 2231 def realkey(item):
2225 2232 return key(item.item)
2226 2233 self.items = deque(sorted(self.items, key=realkey, reverse=reverse))
2227 2234
2228 2235 # Find out where the object under the cursor went
2229 2236 cury = self.cury
2230 2237 for (i, item) in enumerate(self.items):
2231 2238 if item is curitem:
2232 2239 cury = i
2233 2240 break
2234 2241
2235 2242 self.moveto(self.curx, cury, refresh=True)
2236 2243
2237 2244
2238 2245 class ibrowse(Display):
2239 2246 # Show this many lines from the previous screen when paging horizontally
2240 2247 pageoverlapx = 1
2241 2248
2242 2249 # Show this many lines from the previous screen when paging vertically
2243 2250 pageoverlapy = 1
2244 2251
2245 2252 # Start scrolling when the cursor is less than this number of columns
2246 2253 # away from the left or right screen edge
2247 2254 scrollborderx = 10
2248 2255
2249 2256 # Start scrolling when the cursor is less than this number of lines
2250 2257 # away from the top or bottom screen edge
2251 2258 scrollbordery = 5
2252 2259
2253 2260 # Accelerate by this factor when scrolling horizontally
2254 2261 acceleratex = 1.05
2255 2262
2256 2263 # Accelerate by this factor when scrolling vertically
2257 2264 acceleratey = 1.05
2258 2265
2259 2266 # The maximum horizontal scroll speed
2260 2267 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
2261 2268 maxspeedx = 0.5
2262 2269
2263 2270 # The maximum vertical scroll speed
2264 2271 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
2265 2272 maxspeedy = 0.5
2266 2273
2267 2274 # The maximum number of header lines for browser level
2268 2275 # if the nesting is deeper, only the innermost levels are displayed
2269 2276 maxheaders = 5
2270 2277
2271 2278 # The approximate maximum length of a column entry
2272 2279 maxattrlength = 200
2273 2280
2274 2281 # Styles for various parts of the GUI
2275 2282 style_objheadertext = Style(COLOR_WHITE, COLOR_BLACK, A_BOLD|A_REVERSE)
2276 2283 style_objheadernumber = Style(COLOR_WHITE, COLOR_BLUE, A_BOLD|A_REVERSE)
2277 2284 style_objheaderobject = Style(COLOR_WHITE, COLOR_BLACK, A_REVERSE)
2278 2285 style_colheader = Style(COLOR_BLUE, COLOR_WHITE, A_REVERSE)
2279 2286 style_colheaderhere = Style(COLOR_GREEN, COLOR_BLACK, A_BOLD|A_REVERSE)
2280 2287 style_colheadersep = Style(COLOR_BLUE, COLOR_BLACK, A_REVERSE)
2281 2288 style_number = Style(COLOR_BLUE, COLOR_WHITE, A_REVERSE)
2282 2289 style_numberhere = Style(COLOR_GREEN, COLOR_BLACK, A_BOLD|A_REVERSE)
2283 2290 style_sep = Style(COLOR_BLUE, COLOR_BLACK)
2284 2291 style_data = Style(COLOR_WHITE, COLOR_BLACK)
2285 2292 style_datapad = Style(COLOR_BLUE, COLOR_BLACK, A_BOLD)
2286 2293 style_footer = Style(COLOR_BLACK, COLOR_WHITE)
2287 2294 style_report = Style(COLOR_WHITE, COLOR_BLACK)
2288 2295
2289 2296 # Column separator in header
2290 2297 headersepchar = "|"
2291 2298
2292 2299 # Character for padding data cell entries
2293 2300 datapadchar = "."
2294 2301
2295 2302 # Column separator in data area
2296 2303 datasepchar = "|"
2297 2304
2298 2305 # Character to use for "empty" cell (i.e. for non-existing attributes)
2299 2306 nodatachar = "-"
2300 2307
2308 # Prompt for the goto command
2309 prompt_goto = "goto object #: "
2310
2301 2311 # Maps curses key codes to "function" names
2302 2312 keymap = {
2303 2313 ord("q"): "quit",
2304 2314 curses.KEY_UP: "up",
2305 2315 curses.KEY_DOWN: "down",
2306 2316 curses.KEY_PPAGE: "pageup",
2307 2317 curses.KEY_NPAGE: "pagedown",
2308 2318 curses.KEY_LEFT: "left",
2309 2319 curses.KEY_RIGHT: "right",
2310 2320 curses.KEY_HOME: "home",
2311 2321 curses.KEY_END: "end",
2312 2322 ord("<"): "prevattr",
2313 2323 0x1b: "prevattr", # SHIFT-TAB
2314 2324 ord(">"): "nextattr",
2315 2325 ord("\t"):"nextattr", # TAB
2316 2326 ord("p"): "pick",
2317 2327 ord("P"): "pickattr",
2318 2328 ord("C"): "pickallattrs",
2319 2329 ord("m"): "pickmarked",
2320 2330 ord("M"): "pickmarkedattr",
2321 2331 ord("\n"): "enterdefault",
2322 2332 # FIXME: What's happening here?
2323 2333 8: "leave",
2324 2334 127: "leave",
2325 2335 curses.KEY_BACKSPACE: "leave",
2326 2336 ord("x"): "leave",
2327 2337 ord("h"): "help",
2328 2338 ord("e"): "enter",
2329 2339 ord("E"): "enterattr",
2330 2340 ord("d"): "detail",
2331 2341 ord("D"): "detailattr",
2332 2342 ord(" "): "tooglemark",
2333 2343 ord("r"): "markrange",
2334 2344 ord("v"): "sortattrasc",
2335 2345 ord("V"): "sortattrdesc",
2346 ord("g"): "goto",
2336 2347 }
2337 2348
2338 2349 def __init__(self, *attrs):
2339 2350 """
2340 2351 Create a new browser. If ``attrs`` is not empty, it is the list
2341 2352 of attributes that will be displayed in the browser, otherwise
2342 2353 these will be determined by the objects on screen.
2343 2354 """
2344 2355 self.attrs = attrs
2345 2356
2346 2357 # Stack of browser levels
2347 2358 self.levels = []
2348 2359 # how many colums to scroll (Changes when accelerating)
2349 2360 self.stepx = 1.
2350 2361
2351 2362 # how many rows to scroll (Changes when accelerating)
2352 2363 self.stepy = 1.
2353 2364
2354 2365 # Beep on the edges of the data area? (Will be set to ``False``
2355 2366 # once the cursor hits the edge of the screen, so we don't get
2356 2367 # multiple beeps).
2357 2368 self._dobeep = True
2358 2369
2359 2370 # Cache for registered ``curses`` colors and styles.
2360 2371 self._styles = {}
2361 2372 self._colors = {}
2362 2373 self._maxcolor = 1
2363 2374
2364 2375 # How many header lines do we want to paint (the numbers of levels
2365 2376 # we have, but with an upper bound)
2366 2377 self._headerlines = 1
2367 2378
2368 2379 # Index of first header line
2369 2380 self._firstheaderline = 0
2370 2381
2371 2382 # curses window
2372 2383 self.scr = None
2373 2384 # report in the footer line (error, executed command etc.)
2374 2385 self._report = None
2375 2386
2376 2387 # value to be returned to the caller (set by commands)
2377 2388 self.returnvalue = None
2378 2389
2390 # The mode the browser is in
2391 # e.g. normal browsing or entering an argument for a command
2392 self.mode = "default"
2393
2394 # The partially entered row number for the goto command
2395 self.goto = ""
2396
2379 2397 def nextstepx(self, step):
2380 2398 """
2381 2399 Accelerate horizontally.
2382 2400 """
2383 2401 return max(1., min(step*self.acceleratex,
2384 2402 self.maxspeedx*self.levels[-1].mainsizex))
2385 2403
2386 2404 def nextstepy(self, step):
2387 2405 """
2388 2406 Accelerate vertically.
2389 2407 """
2390 2408 return max(1., min(step*self.acceleratey,
2391 2409 self.maxspeedy*self.levels[-1].mainsizey))
2392 2410
2393 2411 def getstyle(self, style):
2394 2412 """
2395 2413 Register the ``style`` with ``curses`` or get it from the cache,
2396 2414 if it has been registered before.
2397 2415 """
2398 2416 try:
2399 2417 return self._styles[style.fg, style.bg, style.attrs]
2400 2418 except KeyError:
2401 2419 attrs = 0
2402 2420 for b in A2CURSES:
2403 2421 if style.attrs & b:
2404 2422 attrs |= A2CURSES[b]
2405 2423 try:
2406 2424 color = self._colors[style.fg, style.bg]
2407 2425 except KeyError:
2408 2426 curses.init_pair(
2409 2427 self._maxcolor,
2410 2428 COLOR2CURSES[style.fg],
2411 2429 COLOR2CURSES[style.bg]
2412 2430 )
2413 2431 color = curses.color_pair(self._maxcolor)
2414 2432 self._colors[style.fg, style.bg] = color
2415 2433 self._maxcolor += 1
2416 2434 c = color | attrs
2417 2435 self._styles[style.fg, style.bg, style.attrs] = c
2418 2436 return c
2419 2437
2420 2438 def format(self, value):
2421 2439 """
2422 2440 Formats one attribute and returns an ``(alignment, string, style)``
2423 2441 tuple.
2424 2442 """
2425 2443 if value is None:
2426 2444 return (-1, repr(value), style_type_none)
2427 2445 elif isinstance(value, str):
2428 2446 return (-1, repr(value.expandtabs(tab))[1:-1], style_default)
2429 2447 elif isinstance(value, unicode):
2430 2448 return (-1, repr(value.expandtabs(tab))[2:-1], style_default)
2431 2449 elif isinstance(value, datetime.datetime):
2432 2450 # Don't use strftime() here, as this requires year >= 1900
2433 2451 return (-1, "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
2434 2452 (value.year, value.month, value.day,
2435 2453 value.hour, value.minute, value.second,
2436 2454 value.microsecond),
2437 2455 style_type_datetime)
2438 2456 elif isinstance(value, datetime.date):
2439 2457 return (-1, "%04d-%02d-%02d" % \
2440 2458 (value.year, value.month, value.day),
2441 2459 style_type_datetime)
2442 2460 elif isinstance(value, datetime.time):
2443 2461 return (-1, "%02d:%02d:%02d.%06d" % \
2444 2462 (value.hour, value.minute, value.second,
2445 2463 value.microsecond),
2446 2464 style_type_datetime)
2447 2465 elif isinstance(value, datetime.timedelta):
2448 2466 return (-1, repr(value), style_type_datetime)
2449 2467 elif isinstance(value, bool):
2450 2468 return (-1, repr(value), style_type_bool)
2451 2469 elif isinstance(value, (int, long, float)):
2452 2470 return (1, repr(value), style_type_number)
2453 2471 elif isinstance(value, complex):
2454 2472 return (-1, repr(value), style_type_number)
2455 2473 elif isinstance(value, Exception):
2456 2474 if value.__class__.__module__ == "exceptions":
2457 2475 value = "%s: %s" % (value.__class__.__name__, value)
2458 2476 else:
2459 2477 value = "%s.%s: %s" % \
2460 2478 (value.__class__.__module__, value.__class__.__name__,
2461 2479 value)
2462 2480 return (-1, value, style_error)
2463 2481 return (-1, repr(value), style_default)
2464 2482
2465 2483 def addstr(self, y, x, begx, endx, text, style):
2466 2484 """
2467 2485 A version of ``curses.addstr()`` that can handle ``x`` coordinates
2468 2486 that are outside the screen.
2469 2487 """
2470 2488 text2 = text[max(0, begx-x):max(0, endx-x)]
2471 2489 if text2:
2472 2490 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
2473 2491 return len(text)
2474 2492
2475 2493 def addchr(self, y, x, begx, endx, c, l, style):
2476 2494 x0 = max(x, begx)
2477 2495 x1 = min(x+l, endx)
2478 2496 if x1>x0:
2479 2497 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
2480 2498 return l
2481 2499
2482 2500 def _calcheaderlines(self, levels):
2483 2501 # Calculate how many headerlines do we have to display, if we have
2484 2502 # ``levels`` browser levels
2485 2503 if levels is None:
2486 2504 levels = len(self.levels)
2487 2505 self._headerlines = min(self.maxheaders, levels)
2488 2506 self._firstheaderline = levels-self._headerlines
2489 2507
2490 2508 def getstylehere(self, style):
2491 2509 """
2492 2510 Return a style for displaying the original style ``style``
2493 2511 in the row the cursor is on.
2494 2512 """
2495 2513 return Style(style.fg, style.bg, style.attrs | A_BOLD)
2496 2514
2497 2515 def report(self, msg):
2498 2516 """
2499 2517 Store the message ``msg`` for display below the footer line. This
2500 2518 will be displayed as soon as the screen is redrawn.
2501 2519 """
2502 2520 self._report = msg
2503 2521
2504 2522 def enter(self, item, mode, *attrs):
2505 2523 """
2506 2524 Enter the object ``item`` in the mode ``mode``. If ``attrs`` is
2507 2525 specified, it will be used as a fixed list of attributes to display.
2508 2526 """
2509 2527 try:
2510 2528 iterator = xiter(item, mode)
2511 2529 except (KeyboardInterrupt, SystemExit):
2512 2530 raise
2513 2531 except Exception, exc:
2514 2532 curses.beep()
2515 2533 self.report(exc)
2516 2534 else:
2517 2535 self._calcheaderlines(len(self.levels)+1)
2518 2536 level = _BrowserLevel(
2519 2537 self,
2520 2538 item,
2521 2539 iterator,
2522 2540 self.scrsizey-1-self._headerlines-2,
2523 2541 *attrs
2524 2542 )
2525 2543 self.levels.append(level)
2526 2544
2527 2545 def keylabel(self, keycode):
2528 2546 """
2529 2547 Return a pretty name for the ``curses`` key ``keycode`` (used in the
2530 2548 help screen and in reports about unassigned keys).
2531 2549 """
2532 2550 if keycode <= 0xff:
2533 2551 specialsnames = {
2534 2552 ord("\n"): "RETURN",
2535 2553 ord(" "): "SPACE",
2536 2554 ord("\t"): "TAB",
2537 2555 ord("\x7f"): "DELETE",
2538 2556 ord("\x08"): "BACKSPACE",
2539 2557 }
2540 2558 if keycode in specialsnames:
2541 2559 return specialsnames[keycode]
2542 2560 return repr(chr(keycode))
2543 2561 for name in dir(curses):
2544 2562 if name.startswith("KEY_") and getattr(curses, name) == keycode:
2545 2563 return name
2546 2564 return str(keycode)
2547 2565
2548 2566 def beep(self, force=False):
2549 2567 if force or self._dobeep:
2550 2568 curses.beep()
2551 2569 # don't beep again (as long as the same key is pressed)
2552 2570 self._dobeep = False
2553 2571
2554 2572 def cmd_quit(self):
2555 2573 self.returnvalue = None
2556 2574 return True
2557 2575
2558 2576 def cmd_up(self):
2559 2577 level = self.levels[-1]
2560 2578 self.report("up")
2561 2579 level.moveto(level.curx, level.cury-self.stepy)
2562 2580
2563 2581 def cmd_down(self):
2564 2582 level = self.levels[-1]
2565 2583 self.report("down")
2566 2584 level.moveto(level.curx, level.cury+self.stepy)
2567 2585
2568 2586 def cmd_pageup(self):
2569 2587 level = self.levels[-1]
2570 2588 self.report("page up")
2571 2589 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
2572 2590
2573 2591 def cmd_pagedown(self):
2574 2592 level = self.levels[-1]
2575 2593 self.report("page down")
2576 2594 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
2577 2595
2578 2596 def cmd_left(self):
2579 2597 level = self.levels[-1]
2580 2598 self.report("left")
2581 2599 level.moveto(level.curx-self.stepx, level.cury)
2582 2600
2583 2601 def cmd_right(self):
2584 2602 level = self.levels[-1]
2585 2603 self.report("right")
2586 2604 level.moveto(level.curx+self.stepx, level.cury)
2587 2605
2588 2606 def cmd_home(self):
2589 2607 level = self.levels[-1]
2590 2608 self.report("home")
2591 2609 level.moveto(0, level.cury)
2592 2610
2593 2611 def cmd_end(self):
2594 2612 level = self.levels[-1]
2595 2613 self.report("end")
2596 2614 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
2597 2615
2598 2616 def cmd_prevattr(self):
2599 2617 level = self.levels[-1]
2600 2618 if level.displayattr[0] is None or level.displayattr[0] == 0:
2601 2619 self.beep()
2602 2620 else:
2603 2621 self.report("prevattr")
2604 2622 pos = 0
2605 2623 for (i, attrname) in enumerate(level.displayattrs):
2606 2624 if i == level.displayattr[0]-1:
2607 2625 break
2608 2626 pos += level.colwidths[attrname] + 1
2609 2627 level.moveto(pos, level.cury)
2610 2628
2611 2629 def cmd_nextattr(self):
2612 2630 level = self.levels[-1]
2613 2631 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
2614 2632 self.beep()
2615 2633 else:
2616 2634 self.report("nextattr")
2617 2635 pos = 0
2618 2636 for (i, attrname) in enumerate(level.displayattrs):
2619 2637 if i == level.displayattr[0]+1:
2620 2638 break
2621 2639 pos += level.colwidths[attrname] + 1
2622 2640 level.moveto(pos, level.cury)
2623 2641
2624 2642 def cmd_pick(self):
2625 2643 level = self.levels[-1]
2626 2644 self.returnvalue = level.items[level.cury].item
2627 2645 return True
2628 2646
2629 2647 def cmd_pickattr(self):
2630 2648 level = self.levels[-1]
2631 2649 attrname = level.displayattr[1]
2632 2650 if attrname is _default:
2633 2651 curses.beep()
2634 2652 self.report(AttributeError(_attrname(attrname)))
2635 2653 return
2636 2654 attr = _getattr(level.items[level.cury].item, attrname)
2637 2655 if attr is _default:
2638 2656 curses.beep()
2639 2657 self.report(AttributeError(_attrname(attrname)))
2640 2658 else:
2641 2659 self.returnvalue = attr
2642 2660 return True
2643 2661
2644 2662 def cmd_pickallattrs(self):
2645 2663 level = self.levels[-1]
2646 2664 attrname = level.displayattr[1]
2647 2665 if attrname is _default:
2648 2666 curses.beep()
2649 2667 self.report(AttributeError(_attrname(attrname)))
2650 2668 return
2651 2669 result = []
2652 2670 for cache in level.items:
2653 2671 attr = _getattr(cache.item, attrname)
2654 2672 if attr is not _default:
2655 2673 result.append(attr)
2656 2674 self.returnvalue = result
2657 2675 return True
2658 2676
2659 2677 def cmd_pickmarked(self):
2660 2678 level = self.levels[-1]
2661 2679 self.returnvalue = [cache.item for cache in level.items if cache.marked]
2662 2680 return True
2663 2681
2664 2682 def cmd_pickmarkedattr(self):
2665 2683 level = self.levels[-1]
2666 2684 attrname = level.displayattr[1]
2667 2685 if attrname is _default:
2668 2686 curses.beep()
2669 2687 self.report(AttributeError(_attrname(attrname)))
2670 2688 return
2671 2689 result = []
2672 2690 for cache in level.items:
2673 2691 if cache.marked:
2674 2692 attr = _getattr(cache.item, attrname)
2675 2693 if attr is not _default:
2676 2694 result.append(attr)
2677 2695 self.returnvalue = result
2678 2696 return True
2679 2697
2680 2698 def cmd_markrange(self):
2681 2699 level = self.levels[-1]
2682 2700 self.report("markrange")
2683 2701 start = None
2684 2702 if level.items:
2685 2703 for i in xrange(level.cury, -1, -1):
2686 2704 if level.items[i].marked:
2687 2705 start = i
2688 2706 break
2689 2707 if start is None:
2690 2708 self.report(CommandError("no mark before cursor"))
2691 2709 curses.beep()
2692 2710 else:
2693 2711 for i in xrange(start, level.cury+1):
2694 2712 cache = level.items[i]
2695 2713 if not cache.marked:
2696 2714 cache.marked = True
2697 2715 level.marked += 1
2698 2716
2699 2717 def cmd_enterdefault(self):
2700 2718 level = self.levels[-1]
2701 2719 try:
2702 2720 item = level.items[level.cury].item
2703 2721 except IndexError:
2704 2722 self.report(CommandError("No object"))
2705 2723 curses.beep()
2706 2724 else:
2707 2725 self.report("entering object (default mode)...")
2708 2726 self.enter(item, "default")
2709 2727
2710 2728 def cmd_leave(self):
2711 2729 self.report("leave")
2712 2730 if len(self.levels) > 1:
2713 2731 self._calcheaderlines(len(self.levels)-1)
2714 2732 self.levels.pop(-1)
2715 2733 else:
2716 2734 self.report(CommandError("This is the last level"))
2717 2735 curses.beep()
2718 2736
2719 2737 def cmd_enter(self):
2720 2738 level = self.levels[-1]
2721 2739 try:
2722 2740 item = level.items[level.cury].item
2723 2741 except IndexError:
2724 2742 self.report(CommandError("No object"))
2725 2743 curses.beep()
2726 2744 else:
2727 2745 self.report("entering object...")
2728 2746 self.enter(item, None)
2729 2747
2730 2748 def cmd_enterattr(self):
2731 2749 level = self.levels[-1]
2732 2750 attrname = level.displayattr[1]
2733 2751 if attrname is _default:
2734 2752 curses.beep()
2735 2753 self.report(AttributeError(_attrname(attrname)))
2736 2754 return
2737 2755 try:
2738 2756 item = level.items[level.cury].item
2739 2757 except IndexError:
2740 2758 self.report(CommandError("No object"))
2741 2759 curses.beep()
2742 2760 else:
2743 2761 attr = _getattr(item, attrname)
2744 2762 if attr is _default:
2745 2763 self.report(AttributeError(_attrname(attrname)))
2746 2764 else:
2747 2765 self.report("entering object attribute %s..." % _attrname(attrname))
2748 2766 self.enter(attr, None)
2749 2767
2750 2768 def cmd_detail(self):
2751 2769 level = self.levels[-1]
2752 2770 try:
2753 2771 item = level.items[level.cury].item
2754 2772 except IndexError:
2755 2773 self.report(CommandError("No object"))
2756 2774 curses.beep()
2757 2775 else:
2758 2776 self.report("entering detail view for object...")
2759 2777 self.enter(item, "detail")
2760 2778
2761 2779 def cmd_detailattr(self):
2762 2780 level = self.levels[-1]
2763 2781 attrname = level.displayattr[1]
2764 2782 if attrname is _default:
2765 2783 curses.beep()
2766 2784 self.report(AttributeError(_attrname(attrname)))
2767 2785 return
2768 2786 try:
2769 2787 item = level.items[level.cury].item
2770 2788 except IndexError:
2771 2789 self.report(CommandError("No object"))
2772 2790 curses.beep()
2773 2791 else:
2774 2792 attr = _getattr(item, attrname)
2775 2793 if attr is _default:
2776 2794 self.report(AttributeError(_attrname(attrname)))
2777 2795 else:
2778 2796 self.report("entering detail view for attribute...")
2779 2797 self.enter(attr, "detail")
2780 2798
2781 2799 def cmd_tooglemark(self):
2782 2800 level = self.levels[-1]
2783 2801 self.report("toggle mark")
2784 2802 try:
2785 2803 item = level.items[level.cury]
2786 2804 except IndexError: # no items?
2787 2805 pass
2788 2806 else:
2789 2807 if item.marked:
2790 2808 item.marked = False
2791 2809 level.marked -= 1
2792 2810 else:
2793 2811 item.marked = True
2794 2812 level.marked += 1
2795 2813
2796 2814 def cmd_sortattrasc(self):
2797 2815 level = self.levels[-1]
2798 2816 attrname = level.displayattr[1]
2799 2817 if attrname is _default:
2800 2818 curses.beep()
2801 2819 self.report(AttributeError(_attrname(attrname)))
2802 2820 return
2803 2821 self.report("sort by %s (ascending)" % _attrname(attrname))
2804 2822 def key(item):
2805 2823 try:
2806 2824 return _getattr(item, attrname, None)
2807 2825 except (KeyboardInterrupt, SystemExit):
2808 2826 raise
2809 2827 except Exception:
2810 2828 return None
2811 2829 level.sort(key)
2812 2830
2813 2831 def cmd_sortattrdesc(self):
2814 2832 level = self.levels[-1]
2815 2833 attrname = level.displayattr[1]
2816 2834 if attrname is _default:
2817 2835 curses.beep()
2818 2836 self.report(AttributeError(_attrname(attrname)))
2819 2837 return
2820 2838 self.report("sort by %s (descending)" % _attrname(attrname))
2821 2839 def key(item):
2822 2840 try:
2823 2841 return _getattr(item, attrname, None)
2824 2842 except (KeyboardInterrupt, SystemExit):
2825 2843 raise
2826 2844 except Exception:
2827 2845 return None
2828 2846 level.sort(key, reverse=True)
2829 2847
2848 def cmd_goto(self):
2849 self.mode = "goto"
2850 self.goto = ""
2851
2830 2852 def cmd_help(self):
2831 2853 """
2832 2854 The help command
2833 2855 """
2834 2856 for level in self.levels:
2835 2857 if isinstance(level.input, _BrowserHelp):
2836 2858 curses.beep()
2837 2859 self.report(CommandError("help already active"))
2838 2860 return
2839 2861
2840 2862 self.enter(_BrowserHelp(self), "default")
2841 2863
2842 2864 def _dodisplay(self, scr):
2843 2865 """
2844 2866 This method is the workhorse of the browser. It handles screen
2845 2867 drawing and the keyboard.
2846 2868 """
2847 2869 self.scr = scr
2848 2870 curses.halfdelay(1)
2849 2871 footery = 2
2850 2872
2851 2873 keys = []
2852 2874 for (key, cmd) in self.keymap.iteritems():
2853 2875 if cmd == "quit":
2854 2876 keys.append("%s=%s" % (self.keylabel(key), cmd))
2855 2877 for (key, cmd) in self.keymap.iteritems():
2856 2878 if cmd == "help":
2857 2879 keys.append("%s=%s" % (self.keylabel(key), cmd))
2858 2880 helpmsg = " | %s" % " ".join(keys)
2859 2881
2860 2882 scr.clear()
2861 2883 msg = "Fetching first batch of objects..."
2862 2884 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
2863 2885 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
2864 2886 scr.refresh()
2865 2887
2866 2888 lastc = -1
2867 2889
2868 2890 self.levels = []
2869 2891 # enter the first level
2870 2892 self.enter(self.input, xiter(self.input, "default"), *self.attrs)
2871 2893
2872 2894 self._calcheaderlines(None)
2873 2895
2874 2896 while True:
2875 2897 level = self.levels[-1]
2876 2898 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
2877 2899 level.mainsizey = self.scrsizey-1-self._headerlines-footery
2878 2900
2879 2901 # Paint object header
2880 2902 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
2881 2903 lv = self.levels[i]
2882 2904 posx = 0
2883 2905 posy = i-self._firstheaderline
2884 2906 endx = self.scrsizex
2885 2907 if i: # not the first level
2886 2908 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
2887 2909 if not self.levels[i-1].exhausted:
2888 2910 msg += "+"
2889 2911 msg += ") "
2890 2912 endx -= len(msg)+1
2891 2913 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
2892 2914 for (style, text) in lv.header:
2893 2915 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
2894 2916 if posx >= endx:
2895 2917 break
2896 2918 if i:
2897 2919 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
2898 2920 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
2899 2921
2900 2922 if not level.items:
2901 2923 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
2902 2924 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", style_error)
2903 2925 scr.clrtobot()
2904 2926 else:
2905 2927 # Paint column headers
2906 2928 scr.move(self._headerlines, 0)
2907 2929 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
2908 2930 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
2909 2931 begx = level.numbersizex+3
2910 2932 posx = begx-level.datastartx
2911 2933 for attrname in level.displayattrs:
2912 2934 strattrname = _attrname(attrname)
2913 2935 cwidth = level.colwidths[attrname]
2914 2936 header = strattrname.ljust(cwidth)
2915 2937 if attrname == level.displayattr[1]:
2916 2938 style = self.style_colheaderhere
2917 2939 else:
2918 2940 style = self.style_colheader
2919 2941 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
2920 2942 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
2921 2943 if posx >= self.scrsizex:
2922 2944 break
2923 2945 else:
2924 2946 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
2925 2947
2926 2948 # Paint rows
2927 2949 posy = self._headerlines+1+level.datastarty
2928 2950 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
2929 2951 cache = level.items[i]
2930 2952 if i == level.cury:
2931 2953 style = self.style_numberhere
2932 2954 else:
2933 2955 style = self.style_number
2934 2956
2935 2957 posy = self._headerlines+1+i-level.datastarty
2936 2958 posx = begx-level.datastartx
2937 2959
2938 2960 scr.move(posy, 0)
2939 2961 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
2940 2962 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
2941 2963
2942 2964 for attrname in level.displayattrs:
2943 2965 cwidth = level.colwidths[attrname]
2944 2966 try:
2945 2967 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
2946 2968 except KeyError:
2947 2969 align = 2
2948 2970 style = style_nodata
2949 2971 padstyle = self.style_datapad
2950 2972 sepstyle = self.style_sep
2951 2973 if i == level.cury:
2952 2974 padstyle = self.getstylehere(padstyle)
2953 2975 sepstyle = self.getstylehere(sepstyle)
2954 2976 if align == 2:
2955 2977 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
2956 2978 else:
2957 2979 if align == 1:
2958 2980 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
2959 2981 elif align == 0:
2960 2982 pad1 = (cwidth-length)//2
2961 2983 pad2 = cwidth-length-len(pad1)
2962 2984 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
2963 2985 for (style, text) in parts:
2964 2986 if i == level.cury:
2965 2987 style = self.getstylehere(style)
2966 2988 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
2967 2989 if posx >= self.scrsizex:
2968 2990 break
2969 2991 if align == -1:
2970 2992 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
2971 2993 elif align == 0:
2972 2994 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
2973 2995 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
2974 2996 else:
2975 2997 scr.clrtoeol()
2976 2998
2977 2999 # Add blank row headers for the rest of the screen
2978 3000 for posy in xrange(posy+1, self.scrsizey-2):
2979 3001 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
2980 3002 scr.clrtoeol()
2981 3003
2982 3004 posy = self.scrsizey-footery
2983 3005 # Display footer
2984 3006 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
2985 3007
2986 3008 if level.exhausted:
2987 3009 flag = ""
2988 3010 else:
2989 3011 flag = "+"
2990 3012
2991 3013 endx = self.scrsizex-len(helpmsg)-1
2992 3014 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
2993 3015
2994 3016 posx = 0
2995 3017 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
2996 3018 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
2997 3019 try:
2998 3020 item = level.items[level.cury].item
2999 3021 except IndexError: # empty
3000 3022 pass
3001 3023 else:
3002 3024 for (nostyle, text) in xrepr(item, "footer"):
3003 3025 if not isinstance(nostyle, int):
3004 3026 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
3005 3027 if posx >= endx:
3006 3028 break
3029
3030 attrstyle = [(style_default, "no attribute")]
3007 3031 attrname = level.displayattr[1]
3008 3032 if attrname is not _default and attrname is not None:
3009 3033 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
3010 3034 posx += self.addstr(posy, posx, 0, endx, _attrname(attrname), self.style_footer)
3011 3035 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
3012 3036 try:
3013 3037 attr = _getattr(item, attrname)
3014 3038 except (SystemExit, KeyboardInterrupt):
3015 3039 raise
3016 3040 except Exception, exc:
3017 3041 attr = exc
3018 for (nostyle, text) in xrepr(attr, "footer"):
3042 if attr is not _default:
3043 attrstyle = xrepr(attr, "footer")
3044 for (nostyle, text) in attrstyle:
3019 3045 if not isinstance(nostyle, int):
3020 3046 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
3021 3047 if posx >= endx:
3022 3048 break
3023 3049
3024 #else:
3025 #msg += ": %s > no attribute" % xrepr(level.items[level.cury].item, "footer")
3026 #self.addstr(posy, 1, 1, self.scrsizex-len(helpmsg)-1, msg, self.style_footer)
3027
3028 # Display report
3029 if self._report is not None:
3030 if isinstance(self._report, Exception):
3031 style = self.getstyle(style_error)
3032 if self._report.__class__.__module__ == "exceptions":
3033 msg = "%s: %s" % \
3034 (self._report.__class__.__name__, self._report)
3035 else:
3036 msg = "%s.%s: %s" % \
3037 (self._report.__class__.__module__,
3038 self._report.__class__.__name__, self._report)
3050 try:
3051 # Display goto input prompt
3052 if self.mode == "goto":
3053 scr.addstr(self.scrsizey-1, 0, self.prompt_goto + self.goto, self.getstyle(style_default))
3054 # Display report
3039 3055 else:
3040 style = self.getstyle(self.style_report)
3041 msg = self._report
3042 try:
3043 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
3044 except curses.err:
3045 # Protect against error from writing to the last line
3046 pass
3047 self._report = None
3048 else:
3049 scr.move(self.scrsizey-1, 0)
3056 if self._report is not None:
3057 if isinstance(self._report, Exception):
3058 style = self.getstyle(style_error)
3059 if self._report.__class__.__module__ == "exceptions":
3060 msg = "%s: %s" % \
3061 (self._report.__class__.__name__, self._report)
3062 else:
3063 msg = "%s.%s: %s" % \
3064 (self._report.__class__.__module__,
3065 self._report.__class__.__name__, self._report)
3066 else:
3067 style = self.getstyle(self.style_report)
3068 msg = self._report
3069 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
3070 self._report = None
3071 else:
3072 scr.move(self.scrsizey-1, 0)
3073 except curses.error:
3074 # Protect against error from writing to the last line
3075 pass
3050 3076 scr.clrtoeol()
3051 3077
3052 3078 # Position cursor
3053 scr.move(
3054 1+self._headerlines+level.cury-level.datastarty,
3055 level.numbersizex+3+level.curx-level.datastartx
3056 )
3079 if self.mode == "goto":
3080 scr.move(self.scrsizey-1, len(self.prompt_goto)+len(self.goto))
3081 else:
3082 scr.move(
3083 1+self._headerlines+level.cury-level.datastarty,
3084 level.numbersizex+3+level.curx-level.datastartx
3085 )
3057 3086 scr.refresh()
3058 3087
3059 3088 # Check keyboard
3060 3089 while True:
3061 3090 c = scr.getch()
3062 # if no key is pressed slow down and beep again
3063 if c == -1:
3064 self.stepx = 1.
3065 self.stepy = 1.
3066 self._dobeep = True
3091 if self.mode == "goto":
3092 if ord("0") <= c <= ord("9"):
3093 self.goto += chr(c)
3094 break # Redisplay
3095 elif c in (8, 127, curses.KEY_BACKSPACE, ord("x")):
3096 if self.goto:
3097 self.goto = self.goto[:-1]
3098 break
3099 else:
3100 curses.beep()
3101 elif c == ord("\n"):
3102 self.mode = "default"
3103 if self.goto:
3104 level.moveto(level.curx, int(self.goto))
3105 break
3106 else:
3107 curses.beep()
3067 3108 else:
3068 # if a different key was pressed slow down and beep too
3069 if c != lastc:
3070 lastc = c
3109 # if no key is pressed slow down and beep again
3110 if c == -1:
3071 3111 self.stepx = 1.
3072 3112 self.stepy = 1.
3073 3113 self._dobeep = True
3074 cmdname = self.keymap.get(c, None)
3075 if cmdname is None:
3076 self.report(
3077 UnassignedKeyError("Unassigned key %s" %
3078 self.keylabel(c)))
3079 3114 else:
3080 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
3081 if cmdfunc is None:
3115 # if a different key was pressed slow down and beep too
3116 if c != lastc:
3117 lastc = c
3118 self.stepx = 1.
3119 self.stepy = 1.
3120 self._dobeep = True
3121 cmdname = self.keymap.get(c, None)
3122 if cmdname is None:
3082 3123 self.report(
3083 UnknownCommandError("Unknown command %r" %
3084 (cmdname,)))
3085 elif cmdfunc():
3086 returnvalue = self.returnvalue
3087 self.returnvalue = None
3088 return returnvalue
3089 self.stepx = self.nextstepx(self.stepx)
3090 self.stepy = self.nextstepy(self.stepy)
3091 curses.flushinp() # get rid of type ahead
3092 break # Redisplay
3124 UnassignedKeyError("Unassigned key %s" %
3125 self.keylabel(c)))
3126 else:
3127 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
3128 if cmdfunc is None:
3129 self.report(
3130 UnknownCommandError("Unknown command %r" %
3131 (cmdname,)))
3132 elif cmdfunc():
3133 returnvalue = self.returnvalue
3134 self.returnvalue = None
3135 return returnvalue
3136 self.stepx = self.nextstepx(self.stepx)
3137 self.stepy = self.nextstepy(self.stepy)
3138 curses.flushinp() # get rid of type ahead
3139 break # Redisplay
3093 3140 self.scr = None
3094 3141
3095 3142 def display(self):
3096 3143 return curses.wrapper(self._dodisplay)
3097 3144
3098 3145 defaultdisplay = ibrowse
3099 3146 __all__.append("ibrowse")
3100 3147 else:
3101 3148 # No curses (probably Windows) => use ``idump`` as the default display.
3102 3149 defaultdisplay = idump
3103 3150
3104 3151
3105 3152 # If we're running under IPython, install an IPython displayhook that
3106 3153 # returns the object from Display.display(), else install a displayhook
3107 3154 # directly as sys.displayhook
3108 3155 try:
3109 3156 from IPython import ipapi
3110 3157 api = ipapi.get()
3111 3158 except (ImportError, AttributeError):
3112 3159 api = None
3113 3160
3114 3161 if api is not None:
3115 3162 def displayhook(self, obj):
3116 3163 if isinstance(obj, type) and issubclass(obj, Table):
3117 3164 obj = obj()
3118 3165 if isinstance(obj, Table):
3119 3166 obj = obj | defaultdisplay
3120 3167 if isinstance(obj, Display):
3121 3168 return obj.display()
3122 3169 else:
3123 3170 raise ipapi.TryNext
3124 3171 api.set_hook("result_display", displayhook)
3125 3172 else:
3126 3173 def installdisplayhook():
3127 3174 _originalhook = sys.displayhook
3128 3175 def displayhook(obj):
3129 3176 if isinstance(obj, type) and issubclass(obj, Table):
3130 3177 obj = obj()
3131 3178 if isinstance(obj, Table):
3132 3179 obj = obj | defaultdisplay
3133 3180 if isinstance(obj, Display):
3134 3181 return obj.display()
3135 3182 else:
3136 3183 _originalhook(obj)
3137 3184 sys.displayhook = displayhook
3138 3185 installdisplayhook()
General Comments 0
You need to be logged in to leave comments. Login now