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