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