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