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