##// END OF EJS Templates
Walter's ipipe patch:...
vivainio -
Show More
@@ -8,10 +8,10 b' objects imported this way starts with ``i`` to minimize collisions.'
8 8 ``ipipe`` supports "pipeline expressions", which is something resembling Unix
9 9 pipes. An example is:
10 10
11 iwalk | ifilter("name.endswith('.py')") | isort("size")
11 >>> ienv | isort("_.key.lower()")
12
13 This gives a listing of all environment variables sorted by name.
12 14
13 This gives a listing of all files in the current directory (and subdirectories)
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
@@ -31,6 +31,78 b' There are three types of objects in a pipeline expression:'
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
35
36 Adding support for pipeline expressions to your own objects can be done through
37 three extensions points (all of them optional):
38
39 * An object that will be displayed as a row by a ``Display`` object should
40 implement the method ``__xattrs__(self, mode)``. This method must return a
41 sequence of attribute names. This sequence may also contain integers, which
42 will be treated as sequence indizes. Also supported is ``None``, which uses
43 the object itself and callables which will be called with the object as the
44 an argument. If ``__xattrs__()`` isn't implemented ``(None,)`` will be used as
45 the attribute sequence (i.e. the object itself (it's ``repr()`` format) will
46 be being displayed. The global function ``xattrs()`` implements this
47 functionality.
48
49 * When an object ``foo`` is displayed in the header (or footer) of the browser
50 ``foo.__xrepr__("header")`` (or ``foo.__xrepr__("footer")``) is called.
51 Currently only ``"header"`` and ``"footer"`` are used as the mode argument.
52 When the method doesn't recognize the mode argument, it should fall back to
53 the ``repr()`` output. If the method ``__xrepr__()`` isn't defined the browser
54 falls back to ``repr()``. The global function ``xrepr()`` implements this
55 functionality.
56
57 * Objects that can be iterated by ``Pipe``s must implement the method
58 ``__xiter__(self, mode)``. ``mode`` can take the following values:
59
60 - ``"default"``: This is the default value and ist always used by pipeline
61 expressions. Other values are only used in the browser.
62 - ``None``: This value is passed by the browser. The object must return an
63 iterable of ``XMode`` objects describing all modes supported by the object.
64 (This should never include ``"default"`` or ``None``).
65 - Any other value that the object supports.
66
67 The global function ``xiter()`` can be called to get such an iterator. If
68 the method ``_xiter__`` isn't implemented, ``xiter()`` falls back to
69 ``__iter__``. In addition to that, dictionaries and modules receive special
70 treatment (returning an iterator over ``(key, value)`` pairs). This makes it
71 possible to use dictionaries and modules in pipeline expressions, for example:
72
73 >>> import sys
74 >>> sys | ifilter("isinstance(_.value, int)") | idump
75 key |value
76 api_version| 1012
77 dllhandle | 503316480
78 hexversion | 33817328
79 maxint |2147483647
80 maxunicode | 65535
81 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
82 ...
83
84 Note: The expression strings passed to ``ifilter()`` and ``isort()`` can
85 refer to the object to be filtered or sorted via the variable ``_``. When
86 running under Python 2.4 it's also possible to refer to the attributes of
87 the object directy, i.e.:
88
89 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
90
91 can be replaced with
92
93 >>> sys.modules | ifilter("value is not None") | isort("key.lower()")
94
95 In addition to expression strings, it's possible to pass callables (taking
96 the object as an argument) to ``ifilter()``, ``isort()`` and ``ieval()``:
97
98 >>> sys | ifilter(lambda _:isinstance(_.value, int)) \
99 ... | ieval(lambda _: (_.key, hex(_.value))) | idump
100 0 |1
101 api_version|0x3f4
102 dllhandle |0x1e000000
103 hexversion |0x20402f0
104 maxint |0x7fffffff
105 maxunicode |0xffff
34 106 """
35 107
36 108 import sys, os, os.path, stat, glob, new, csv, datetime
@@ -88,6 +160,34 b' __all__ = ['
88 160 os.stat_float_times(True) # enable microseconds
89 161
90 162
163 class _AttrNamespace(object):
164 """
165 Internal helper class that is used for providing a namespace for evaluating
166 expressions containg attribute names of an object.
167
168 This class can only be used with Python 2.4.
169 """
170 def __init__(self, wrapped):
171 self.wrapped = wrapped
172
173 def __getitem__(self, name):
174 if name == "_":
175 return self.wrapped
176 try:
177 return getattr(self.wrapped, name)
178 except AttributeError:
179 raise KeyError(name)
180
181 # Python 2.3 compatibility
182 # fall back to a real dictionary (containing just the object itself) if we can't
183 # use the _AttrNamespace class in eval()
184 try:
185 eval("_", None, _AttrNamespace(None))
186 except TypeError:
187 def _AttrNamespace(wrapped):
188 return {"_": wrapped}
189
190
91 191 _default = object()
92 192
93 193 def item(iterator, index, default=_default):
@@ -124,23 +224,6 b' def item(iterator, index, default=_default):'
124 224 return default
125 225
126 226
127 class _AttrNamespace(object):
128 """
129 Internal helper that is used for providing a namespace for evaluating
130 expressions containg attribute names of an object.
131 """
132 def __init__(self, wrapped):
133 self.wrapped = wrapped
134
135 def __getitem__(self, name):
136 if name == "_":
137 return self.wrapped
138 try:
139 return getattr(self.wrapped, name)
140 except AttributeError:
141 raise KeyError(name)
142
143
144 227 class Table(object):
145 228 """
146 229 A ``Table`` is an object that produces items (just like a normal Python
@@ -148,6 +231,10 b' class Table(object):'
148 231 expression. The displayhook will open the default browser for such an object
149 232 (instead of simply printing the ``repr()`` result).
150 233 """
234
235 # We want to support ``foo`` and ``foo()`` in pipeline expression:
236 # So we implement the required operators (``|`` and ``+``) in the metaclass,
237 # instantiate the class and forward the operator to the instance
151 238 class __metaclass__(type):
152 239 def __iter__(self):
153 240 return iter(self())
@@ -174,18 +261,23 b' class Table(object):'
174 261 return False
175 262
176 263 def __or__(self, other):
264 # autoinstantiate right hand side
177 265 if isinstance(other, type) and issubclass(other, (Table, Display)):
178 266 other = other()
267 # treat simple strings and functions as ``ieval`` instances
179 268 elif not isinstance(other, Display) and not isinstance(other, Table):
180 269 other = ieval(other)
270 # forward operations to the right hand side
181 271 return other.__ror__(self)
182 272
183 273 def __add__(self, other):
274 # autoinstantiate right hand side
184 275 if isinstance(other, type) and issubclass(other, Table):
185 276 other = other()
186 277 return ichain(self, other)
187 278
188 279 def __radd__(self, other):
280 # autoinstantiate left hand side
189 281 if isinstance(other, type) and issubclass(other, Table):
190 282 other = other()
191 283 return ichain(other, self)
@@ -206,6 +298,7 b' class Pipe(Table):'
206 298 return input | self()
207 299
208 300 def __ror__(self, input):
301 # autoinstantiate left hand side
209 302 if isinstance(input, type) and issubclass(input, Table):
210 303 input = input()
211 304 self.input = input
@@ -249,9 +342,7 b' def _attrname(name):'
249 342 def xrepr(item, mode):
250 343 try:
251 344 func = item.__xrepr__
252 except (KeyboardInterrupt, SystemExit):
253 raise
254 except Exception:
345 except AttributeError:
255 346 return repr(item)
256 347 else:
257 348 return func(mode)
@@ -287,7 +378,7 b' def xiter(item, mode):'
287 378 def items(item):
288 379 fields = ("key", "value")
289 380 for key in sorted(item.__dict__):
290 yield Fields(fields, key, getattr(item, key))
381 yield Fields(fields, key=key, value=getattr(item, key))
291 382 return items(item)
292 383 elif isinstance(item, basestring):
293 384 if not len(item):
@@ -526,7 +617,7 b' class ifile(object):'
526 617 "size", "blocks", "blksize", "isdir", "islink",
527 618 "mimetype", "encoding"
528 619 )
529 return ("name","type", "size", "access", "owner", "group", "mdate")
620 return ("name", "type", "size", "access", "owner", "group", "mdate")
530 621
531 622 def __xrepr__(self, mode):
532 623 if mode == "header" or mode == "footer":
@@ -1085,6 +1176,7 b' class idump(Display):'
1085 1176 row[attrname] = (value, text)
1086 1177 rows.append(row)
1087 1178
1179 stream.write("\n")
1088 1180 for (i, attrname) in enumerate(self.attrs):
1089 1181 stream.write(_attrname(attrname))
1090 1182 spc = colwidths[attrname] - len(_attrname(attrname))
@@ -1157,7 +1249,16 b' class idump(Display):'
1157 1249
1158 1250
1159 1251 class XMode(object):
1252 """
1253 An ``XMode`` object describes one enter mode available for an object
1254 """
1160 1255 def __init__(self, object, mode, title=None, description=None):
1256 """
1257 Create a new ``XMode`` object for the object ``object``. This object
1258 must support the enter mode ``mode`` (i.e. ``object.__xiter__(mode)``
1259 must return an iterable). ``title`` and ``description`` will be
1260 displayed in the browser when selecting among the available modes.
1261 """
1161 1262 self.object = object
1162 1263 self.mode = mode
1163 1264 self.title = title
@@ -1234,7 +1335,7 b' pagedown'
1234 1335 Move the cursor down one page (minus overlap).
1235 1336
1236 1337 pageup
1237 Move the cursor up one page.
1338 Move the cursor up one page (minus overlap).
1238 1339
1239 1340 left
1240 1341 Move the cursor left.
@@ -1257,8 +1358,8 b' pickattr'
1257 1358 'Pick' the attribute under the cursor (i.e. the row/column the cursor is on).
1258 1359
1259 1360 pickallattrs
1260 Pick' the complete column under the cursor (i.e. the attribute under the cursor
1261 from all currently fetched objects). The attributes will be returned as a list.
1361 Pick' the complete column under the cursor (i.e. the attribute under the cursor)
1362 from all currently fetched objects. These attributes will be returned as a list.
1262 1363
1263 1364 tooglemark
1264 1365 Mark/unmark the object under the cursor. Marked objects have a '!' after the
@@ -1269,16 +1370,17 b' pickmarked'
1269 1370
1270 1371 pickmarkedattr
1271 1372 'Pick' the attribute under the cursor from all marked objects (This returns a
1272 list)
1373 list).
1273 1374
1274 1375 enterdefault
1275 1376 Enter the object under the cursor. (what this mean depends on the object
1276 itself). This opens a new browser 'level'.
1377 itself (i.e. how it implements the '__xiter__' method). This opens a new browser
1378 'level'.
1277 1379
1278 1380 enter
1279 1381 Enter the object under the cursor. If the object provides different enter modes
1280 a menu of all modes will be presented, choice one and enter it (via the 'enter'
1281 or 'enterdefault' command),
1382 a menu of all modes will be presented; choice one and enter it (via the 'enter'
1383 or 'enterdefault' command).
1282 1384
1283 1385 enterattr
1284 1386 Enter the attribute under the cursor.
@@ -1289,7 +1391,7 b' Leave the current browser level and go back to the previous one.'
1289 1391 detail
1290 1392 Show a detail view of the object under the cursor. This shows the name, type,
1291 1393 doc string and value of the object attributes (and it might show more attributes
1292 than in the list view; depending on the object).
1394 than in the list view, depending on the object).
1293 1395
1294 1396 markrange
1295 1397 Mark all objects from the last marked object before the current cursor position
@@ -1310,18 +1412,29 b' This screen.'
1310 1412
1311 1413 if curses is not None:
1312 1414 class UnassignedKeyError(Exception):
1313 pass
1415 """
1416 Exception that is used for reporting unassigned keys.
1417 """
1314 1418
1315 1419
1316 1420 class UnknownCommandError(Exception):
1317 pass
1421 """
1422 Exception that is used for reporting unknown command (this should never
1423 happen).
1424 """
1318 1425
1319 1426
1320 1427 class CommandError(Exception):
1321 pass
1428 """
1429 Exception that is used for reporting that a command can't be executed.
1430 """
1322 1431
1323 1432
1324 1433 class Style(object):
1434 """
1435 Store foreground color, background color and attribute (bold, underlined
1436 etc.) for ``curses``.
1437 """
1325 1438 __slots__ = ("fg", "bg", "attrs")
1326 1439
1327 1440 def __init__(self, fg, bg, attrs=0):
@@ -1331,6 +1444,8 b' if curses is not None:'
1331 1444
1332 1445
1333 1446 class _BrowserCachedItem(object):
1447 # This is used internally by ``ibrowse`` to store a item together with its
1448 # marked status.
1334 1449 __slots__ = ("item", "marked")
1335 1450
1336 1451 def __init__(self, item):
@@ -1339,6 +1454,7 b' if curses is not None:'
1339 1454
1340 1455
1341 1456 class _BrowserHelp(object):
1457 # This is used internally by ``ibrowse`` for displaying the help screen.
1342 1458 def __init__(self, browser):
1343 1459 self.browser = browser
1344 1460
@@ -1378,6 +1494,10 b' if curses is not None:'
1378 1494
1379 1495
1380 1496 class _BrowserLevel(object):
1497 # This is used internally to store the state (iterator, fetch items,
1498 # position of cursor and screen, etc.) of one browser level
1499 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
1500 # a stack.
1381 1501 def __init__(self, browser, input, iterator, mainsizey, *attrs):
1382 1502 self.browser = browser
1383 1503 self.input = input
@@ -1407,6 +1527,7 b' if curses is not None:'
1407 1527 self.calcdisplayattr()
1408 1528
1409 1529 def fetch(self, count):
1530 # Try to fill ``self.items`` with at least ``count`` objects.
1410 1531 have = len(self.items)
1411 1532 while not self.exhausted and have < count:
1412 1533 try:
@@ -1419,7 +1540,11 b' if curses is not None:'
1419 1540 self.items.append(_BrowserCachedItem(item))
1420 1541
1421 1542 def calcdisplayattrs(self):
1543 # Calculate which attributes are available from the objects that are
1544 # currently visible on screen (and store it in ``self.displayattrs``)
1422 1545 attrnames = set()
1546 # If the browser object specifies a fixed list of attributes,
1547 # simply use it.
1423 1548 if self.attrs:
1424 1549 self.displayattrs = self.attrs
1425 1550 else:
@@ -1432,6 +1557,10 b' if curses is not None:'
1432 1557 attrnames.add(attrname)
1433 1558
1434 1559 def getrow(self, i):
1560 # Return a dictinary with the attributes for the object
1561 # ``self.items[i]``. Attribute names are taken from
1562 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
1563 # called before.
1435 1564 row = {}
1436 1565 item = self.items[i].item
1437 1566 for attrname in self.displayattrs:
@@ -1448,7 +1577,11 b' if curses is not None:'
1448 1577 return row
1449 1578
1450 1579 def calcwidths(self):
1451 # Recalculate the displayed fields and their width
1580 # Recalculate the displayed fields and their width.
1581 # ``calcdisplayattrs()'' must have been called and the cache
1582 # for attributes of the objects on screen (``self.displayrows``)
1583 # must have been filled. This returns a dictionary mapping
1584 # colmn names to width.
1452 1585 self.colwidths = {}
1453 1586 for row in self.displayrows:
1454 1587 for attrname in self.displayattrs:
@@ -1467,7 +1600,8 b' if curses is not None:'
1467 1600 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
1468 1601
1469 1602 def calcdisplayattr(self):
1470 # Find out on which attribute the cursor is on
1603 # Find out on which attribute the cursor is on and store this
1604 # information in ``self.displayattr``.
1471 1605 pos = 0
1472 1606 for attrname in self.displayattrs:
1473 1607 if pos+self.colwidths[attrname] >= self.curx:
@@ -1478,6 +1612,10 b' if curses is not None:'
1478 1612 self.displayattr = None
1479 1613
1480 1614 def moveto(self, x, y, refresh=False):
1615 # Move the cursor to the position ``(x,y)`` (in data coordinates,
1616 # not in screen coordinates). If ``refresh`` is true, all cached
1617 # values will be recalculated (e.g. because the list has been
1618 # resorted to screen position etc. are no longer valid).
1481 1619 olddatastarty = self.datastarty
1482 1620 oldx = self.curx
1483 1621 oldy = self.cury
@@ -1506,7 +1644,6 b' if curses is not None:'
1506 1644 endy = min(self.datastarty+self.mainsizey, len(self.items))
1507 1645 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
1508 1646 self.calcwidths()
1509
1510 1647 # Did we scroll vertically => update displayrows
1511 1648 # and various other attributes
1512 1649 elif self.datastarty != olddatastarty:
@@ -1690,27 +1827,61 b' if curses is not None:'
1690 1827 }
1691 1828
1692 1829 def __init__(self, *attrs):
1830 """
1831 Create a new browser. If ``attrs`` is not empty, it is the list
1832 of attributes that will be displayed in the browser, otherwise
1833 these will be determined by the objects on screen.
1834 """
1693 1835 self.attrs = attrs
1836
1837 # Stack of browser levels
1694 1838 self.levels = []
1695 self.stepx = 1. # how many colums to scroll
1696 self.stepy = 1. # how many rows to scroll
1697 self._dobeep = True # Beep on the edges of the data area?
1839 # how many colums to scroll (Changes when accelerating)
1840 self.stepx = 1.
1841
1842 # how many rows to scroll (Changes when accelerating)
1843 self.stepy = 1.
1844
1845 # Beep on the edges of the data area? (Will be set to ``False``
1846 # once the cursor hits the edge of the screen, so we don't get
1847 # multiple beeps).
1848 self._dobeep = True
1849
1850 # Cache for registered ``curses`` colors.
1698 1851 self._colors = {}
1699 1852 self._maxcolor = 1
1700 self._headerlines = 1 # How many header lines do we want to paint (the numbers of levels we have, but with an upper bound)
1701 self._firstheaderline = 0 # Index of first header line
1702 self.scr = None # curses window
1703 self._report = None # report in the footer line
1853
1854 # How many header lines do we want to paint (the numbers of levels
1855 # we have, but with an upper bound)
1856 self._headerlines = 1
1857
1858 # Index of first header line
1859 self._firstheaderline = 0
1860
1861 # curses window
1862 self.scr = None
1863 # report in the footer line (error, executed command etc.)
1864 self._report = None
1704 1865
1705 1866 def nextstepx(self, step):
1867 """
1868 Accelerate horizontally.
1869 """
1706 1870 return max(1., min(step*self.acceleratex,
1707 1871 self.maxspeedx*self.levels[-1].mainsizex))
1708 1872
1709 1873 def nextstepy(self, step):
1874 """
1875 Accelerate vertically.
1876 """
1710 1877 return max(1., min(step*self.acceleratey,
1711 1878 self.maxspeedy*self.levels[-1].mainsizey))
1712 1879
1713 1880 def getstyle(self, style):
1881 """
1882 Register the ``style`` with ``curses`` or get it from the cache,
1883 if it has been registered before.
1884 """
1714 1885 try:
1715 1886 return self._colors[style.fg, style.bg] | style.attrs
1716 1887 except KeyError:
@@ -1722,12 +1893,20 b' if curses is not None:'
1722 1893 return c
1723 1894
1724 1895 def addstr(self, y, x, begx, endx, s, style):
1896 """
1897 A version of ``curses.addstr()`` that can handle ``x`` coordinates
1898 that are outside the screen.
1899 """
1725 1900 s2 = s[max(0, begx-x):max(0, endx-x)]
1726 1901 if s2:
1727 1902 self.scr.addstr(y, max(x, begx), s2, self.getstyle(style))
1728 1903 return len(s)
1729 1904
1730 1905 def format(self, value):
1906 """
1907 Formats one attribute and returns an ``(alignment, string, style)``
1908 tuple.
1909 """
1731 1910 if value is None:
1732 1911 return (-1, repr(value), self.style_type_none)
1733 1912 elif isinstance(value, str):
@@ -1769,6 +1948,8 b' if curses is not None:'
1769 1948 return (-1, repr(value), self.style_default)
1770 1949
1771 1950 def _calcheaderlines(self, levels):
1951 # Calculate how many headerlines do we have to display, if we have
1952 # ``levels`` browser levels
1772 1953 if levels is None:
1773 1954 levels = len(self.levels)
1774 1955 self._headerlines = min(self.maxheaders, levels)
@@ -1782,9 +1963,17 b' if curses is not None:'
1782 1963 return Style(style.fg, style.bg, style.attrs | curses.A_BOLD)
1783 1964
1784 1965 def report(self, msg):
1966 """
1967 Store the message ``msg`` for display below the footer line. This
1968 will be displayed as soon as the screen is redrawn.
1969 """
1785 1970 self._report = msg
1786 1971
1787 1972 def enter(self, item, mode, *attrs):
1973 """
1974 Enter the object ``item`` in the mode ``mode``. If ``attrs`` is
1975 specified, it will be used as a fixed list of attributes to display.
1976 """
1788 1977 try:
1789 1978 iterator = xiter(item, mode)
1790 1979 except (KeyboardInterrupt, SystemExit):
@@ -1804,6 +1993,10 b' if curses is not None:'
1804 1993 self.levels.append(level)
1805 1994
1806 1995 def keylabel(self, keycode):
1996 """
1997 Return a pretty name for the ``curses`` key ``keycode`` (used in the
1998 help screen and in reports about unassigned keys).
1999 """
1807 2000 if keycode <= 0xff:
1808 2001 specialsnames = {
1809 2002 ord("\n"): "RETURN",
@@ -1821,6 +2014,9 b' if curses is not None:'
1821 2014 return str(keycode)
1822 2015
1823 2016 def cmd_help(self):
2017 """
2018 The help command
2019 """
1824 2020 for level in self.levels:
1825 2021 if isinstance(level.input, _BrowserHelp):
1826 2022 curses.beep()
@@ -1830,6 +2026,10 b' if curses is not None:'
1830 2026 self.enter(_BrowserHelp(self), "default")
1831 2027
1832 2028 def _dodisplay(self, scr):
2029 """
2030 This method is the workhorse of the browser. It handles screen
2031 drawing and the keyboard.
2032 """
1833 2033 self.scr = scr
1834 2034 curses.halfdelay(1)
1835 2035 footery = 2
@@ -2144,6 +2344,7 b' if curses is not None:'
2144 2344 defaultdisplay = ibrowse
2145 2345 __all__.append("ibrowse")
2146 2346 else:
2347 # No curses (probably Windows) => use ``idump`` as the default display.
2147 2348 defaultdisplay = idump
2148 2349
2149 2350
General Comments 0
You need to be logged in to leave comments. Login now