Show More
@@ -8,10 +8,10 b' objects imported this way starts with ``i`` to minimize collisions.' | |||||
8 | ``ipipe`` supports "pipeline expressions", which is something resembling Unix |
|
8 | ``ipipe`` supports "pipeline expressions", which is something resembling Unix | |
9 | pipes. An example is: |
|
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 | There are three types of objects in a pipeline expression: |
|
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 | expression. If a pipeline expression doesn't end in a display object a default |
|
31 | expression. If a pipeline expression doesn't end in a display object a default | |
32 | display objects will be used. One example is `οΏ½browse`` which is a ``curses`` |
|
32 | display objects will be used. One example is `οΏ½browse`` which is a ``curses`` | |
33 | based browser. |
|
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 | import sys, os, os.path, stat, glob, new, csv, datetime |
|
108 | import sys, os, os.path, stat, glob, new, csv, datetime | |
@@ -88,6 +160,34 b' __all__ = [' | |||||
88 | os.stat_float_times(True) # enable microseconds |
|
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 | _default = object() |
|
191 | _default = object() | |
92 |
|
192 | |||
93 | def item(iterator, index, default=_default): |
|
193 | def item(iterator, index, default=_default): | |
@@ -124,23 +224,6 b' def item(iterator, index, default=_default):' | |||||
124 | return default |
|
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 | class Table(object): |
|
227 | class Table(object): | |
145 | """ |
|
228 | """ | |
146 | A ``Table`` is an object that produces items (just like a normal Python |
|
229 | A ``Table`` is an object that produces items (just like a normal Python | |
@@ -148,6 +231,10 b' class Table(object):' | |||||
148 | expression. The displayhook will open the default browser for such an object |
|
231 | expression. The displayhook will open the default browser for such an object | |
149 | (instead of simply printing the ``repr()`` result). |
|
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 | class __metaclass__(type): |
|
238 | class __metaclass__(type): | |
152 | def __iter__(self): |
|
239 | def __iter__(self): | |
153 | return iter(self()) |
|
240 | return iter(self()) | |
@@ -174,18 +261,23 b' class Table(object):' | |||||
174 | return False |
|
261 | return False | |
175 |
|
262 | |||
176 | def __or__(self, other): |
|
263 | def __or__(self, other): | |
|
264 | # autoinstantiate right hand side | |||
177 | if isinstance(other, type) and issubclass(other, (Table, Display)): |
|
265 | if isinstance(other, type) and issubclass(other, (Table, Display)): | |
178 | other = other() |
|
266 | other = other() | |
|
267 | # treat simple strings and functions as ``ieval`` instances | |||
179 | elif not isinstance(other, Display) and not isinstance(other, Table): |
|
268 | elif not isinstance(other, Display) and not isinstance(other, Table): | |
180 | other = ieval(other) |
|
269 | other = ieval(other) | |
|
270 | # forward operations to the right hand side | |||
181 | return other.__ror__(self) |
|
271 | return other.__ror__(self) | |
182 |
|
272 | |||
183 | def __add__(self, other): |
|
273 | def __add__(self, other): | |
|
274 | # autoinstantiate right hand side | |||
184 | if isinstance(other, type) and issubclass(other, Table): |
|
275 | if isinstance(other, type) and issubclass(other, Table): | |
185 | other = other() |
|
276 | other = other() | |
186 | return ichain(self, other) |
|
277 | return ichain(self, other) | |
187 |
|
278 | |||
188 | def __radd__(self, other): |
|
279 | def __radd__(self, other): | |
|
280 | # autoinstantiate left hand side | |||
189 | if isinstance(other, type) and issubclass(other, Table): |
|
281 | if isinstance(other, type) and issubclass(other, Table): | |
190 | other = other() |
|
282 | other = other() | |
191 | return ichain(other, self) |
|
283 | return ichain(other, self) | |
@@ -206,6 +298,7 b' class Pipe(Table):' | |||||
206 | return input | self() |
|
298 | return input | self() | |
207 |
|
299 | |||
208 | def __ror__(self, input): |
|
300 | def __ror__(self, input): | |
|
301 | # autoinstantiate left hand side | |||
209 | if isinstance(input, type) and issubclass(input, Table): |
|
302 | if isinstance(input, type) and issubclass(input, Table): | |
210 | input = input() |
|
303 | input = input() | |
211 | self.input = input |
|
304 | self.input = input | |
@@ -249,9 +342,7 b' def _attrname(name):' | |||||
249 | def xrepr(item, mode): |
|
342 | def xrepr(item, mode): | |
250 | try: |
|
343 | try: | |
251 | func = item.__xrepr__ |
|
344 | func = item.__xrepr__ | |
252 | except (KeyboardInterrupt, SystemExit): |
|
345 | except AttributeError: | |
253 | raise |
|
|||
254 | except Exception: |
|
|||
255 | return repr(item) |
|
346 | return repr(item) | |
256 | else: |
|
347 | else: | |
257 | return func(mode) |
|
348 | return func(mode) | |
@@ -287,7 +378,7 b' def xiter(item, mode):' | |||||
287 | def items(item): |
|
378 | def items(item): | |
288 | fields = ("key", "value") |
|
379 | fields = ("key", "value") | |
289 | for key in sorted(item.__dict__): |
|
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 | return items(item) |
|
382 | return items(item) | |
292 | elif isinstance(item, basestring): |
|
383 | elif isinstance(item, basestring): | |
293 | if not len(item): |
|
384 | if not len(item): | |
@@ -526,7 +617,7 b' class ifile(object):' | |||||
526 | "size", "blocks", "blksize", "isdir", "islink", |
|
617 | "size", "blocks", "blksize", "isdir", "islink", | |
527 | "mimetype", "encoding" |
|
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 | def __xrepr__(self, mode): |
|
622 | def __xrepr__(self, mode): | |
532 | if mode == "header" or mode == "footer": |
|
623 | if mode == "header" or mode == "footer": | |
@@ -1085,6 +1176,7 b' class idump(Display):' | |||||
1085 | row[attrname] = (value, text) |
|
1176 | row[attrname] = (value, text) | |
1086 | rows.append(row) |
|
1177 | rows.append(row) | |
1087 |
|
1178 | |||
|
1179 | stream.write("\n") | |||
1088 | for (i, attrname) in enumerate(self.attrs): |
|
1180 | for (i, attrname) in enumerate(self.attrs): | |
1089 | stream.write(_attrname(attrname)) |
|
1181 | stream.write(_attrname(attrname)) | |
1090 | spc = colwidths[attrname] - len(_attrname(attrname)) |
|
1182 | spc = colwidths[attrname] - len(_attrname(attrname)) | |
@@ -1157,7 +1249,16 b' class idump(Display):' | |||||
1157 |
|
1249 | |||
1158 |
|
1250 | |||
1159 | class XMode(object): |
|
1251 | class XMode(object): | |
|
1252 | """ | |||
|
1253 | An ``XMode`` object describes one enter mode available for an object | |||
|
1254 | """ | |||
1160 | def __init__(self, object, mode, title=None, description=None): |
|
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 | self.object = object |
|
1262 | self.object = object | |
1162 | self.mode = mode |
|
1263 | self.mode = mode | |
1163 | self.title = title |
|
1264 | self.title = title | |
@@ -1234,7 +1335,7 b' pagedown' | |||||
1234 | Move the cursor down one page (minus overlap). |
|
1335 | Move the cursor down one page (minus overlap). | |
1235 |
|
1336 | |||
1236 | pageup |
|
1337 | pageup | |
1237 | Move the cursor up one page. |
|
1338 | Move the cursor up one page (minus overlap). | |
1238 |
|
1339 | |||
1239 | left |
|
1340 | left | |
1240 | Move the cursor left. |
|
1341 | Move the cursor left. | |
@@ -1257,8 +1358,8 b' pickattr' | |||||
1257 | 'Pick' the attribute under the cursor (i.e. the row/column the cursor is on). |
|
1358 | 'Pick' the attribute under the cursor (i.e. the row/column the cursor is on). | |
1258 |
|
1359 | |||
1259 | pickallattrs |
|
1360 | pickallattrs | |
1260 | Pick' the complete column under the cursor (i.e. the attribute under the cursor |
|
1361 | Pick' the complete column under the cursor (i.e. the attribute under the cursor) | |
1261 |
from all currently fetched objects |
|
1362 | from all currently fetched objects. These attributes will be returned as a list. | |
1262 |
|
1363 | |||
1263 | tooglemark |
|
1364 | tooglemark | |
1264 | Mark/unmark the object under the cursor. Marked objects have a '!' after the |
|
1365 | Mark/unmark the object under the cursor. Marked objects have a '!' after the | |
@@ -1269,16 +1370,17 b' pickmarked' | |||||
1269 |
|
1370 | |||
1270 | pickmarkedattr |
|
1371 | pickmarkedattr | |
1271 | 'Pick' the attribute under the cursor from all marked objects (This returns a |
|
1372 | 'Pick' the attribute under the cursor from all marked objects (This returns a | |
1272 | list) |
|
1373 | list). | |
1273 |
|
1374 | |||
1274 | enterdefault |
|
1375 | enterdefault | |
1275 | Enter the object under the cursor. (what this mean depends on the object |
|
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 | enter |
|
1380 | enter | |
1279 | Enter the object under the cursor. If the object provides different enter modes |
|
1381 | Enter the object under the cursor. If the object provides different enter modes | |
1280 |
a menu of all modes will be presented |
|
1382 | a menu of all modes will be presented; choice one and enter it (via the 'enter' | |
1281 |
or 'enterdefault' command) |
|
1383 | or 'enterdefault' command). | |
1282 |
|
1384 | |||
1283 | enterattr |
|
1385 | enterattr | |
1284 | Enter the attribute under the cursor. |
|
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 | detail |
|
1391 | detail | |
1290 | Show a detail view of the object under the cursor. This shows the name, type, |
|
1392 | Show a detail view of the object under the cursor. This shows the name, type, | |
1291 | doc string and value of the object attributes (and it might show more attributes |
|
1393 | doc string and value of the object attributes (and it might show more attributes | |
1292 |
than in the list view |
|
1394 | than in the list view, depending on the object). | |
1293 |
|
1395 | |||
1294 | markrange |
|
1396 | markrange | |
1295 | Mark all objects from the last marked object before the current cursor position |
|
1397 | Mark all objects from the last marked object before the current cursor position | |
@@ -1310,18 +1412,29 b' This screen.' | |||||
1310 |
|
1412 | |||
1311 | if curses is not None: |
|
1413 | if curses is not None: | |
1312 | class UnassignedKeyError(Exception): |
|
1414 | class UnassignedKeyError(Exception): | |
1313 |
|
|
1415 | """ | |
|
1416 | Exception that is used for reporting unassigned keys. | |||
|
1417 | """ | |||
1314 |
|
1418 | |||
1315 |
|
1419 | |||
1316 | class UnknownCommandError(Exception): |
|
1420 | class UnknownCommandError(Exception): | |
1317 |
|
|
1421 | """ | |
|
1422 | Exception that is used for reporting unknown command (this should never | |||
|
1423 | happen). | |||
|
1424 | """ | |||
1318 |
|
1425 | |||
1319 |
|
1426 | |||
1320 | class CommandError(Exception): |
|
1427 | class CommandError(Exception): | |
1321 |
|
|
1428 | """ | |
|
1429 | Exception that is used for reporting that a command can't be executed. | |||
|
1430 | """ | |||
1322 |
|
1431 | |||
1323 |
|
1432 | |||
1324 | class Style(object): |
|
1433 | class Style(object): | |
|
1434 | """ | |||
|
1435 | Store foreground color, background color and attribute (bold, underlined | |||
|
1436 | etc.) for ``curses``. | |||
|
1437 | """ | |||
1325 | __slots__ = ("fg", "bg", "attrs") |
|
1438 | __slots__ = ("fg", "bg", "attrs") | |
1326 |
|
1439 | |||
1327 | def __init__(self, fg, bg, attrs=0): |
|
1440 | def __init__(self, fg, bg, attrs=0): | |
@@ -1331,6 +1444,8 b' if curses is not None:' | |||||
1331 |
|
1444 | |||
1332 |
|
1445 | |||
1333 | class _BrowserCachedItem(object): |
|
1446 | class _BrowserCachedItem(object): | |
|
1447 | # This is used internally by ``ibrowse`` to store a item together with its | |||
|
1448 | # marked status. | |||
1334 | __slots__ = ("item", "marked") |
|
1449 | __slots__ = ("item", "marked") | |
1335 |
|
1450 | |||
1336 | def __init__(self, item): |
|
1451 | def __init__(self, item): | |
@@ -1339,6 +1454,7 b' if curses is not None:' | |||||
1339 |
|
1454 | |||
1340 |
|
1455 | |||
1341 | class _BrowserHelp(object): |
|
1456 | class _BrowserHelp(object): | |
|
1457 | # This is used internally by ``ibrowse`` for displaying the help screen. | |||
1342 | def __init__(self, browser): |
|
1458 | def __init__(self, browser): | |
1343 | self.browser = browser |
|
1459 | self.browser = browser | |
1344 |
|
1460 | |||
@@ -1378,6 +1494,10 b' if curses is not None:' | |||||
1378 |
|
1494 | |||
1379 |
|
1495 | |||
1380 | class _BrowserLevel(object): |
|
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 | def __init__(self, browser, input, iterator, mainsizey, *attrs): |
|
1501 | def __init__(self, browser, input, iterator, mainsizey, *attrs): | |
1382 | self.browser = browser |
|
1502 | self.browser = browser | |
1383 | self.input = input |
|
1503 | self.input = input | |
@@ -1407,6 +1527,7 b' if curses is not None:' | |||||
1407 | self.calcdisplayattr() |
|
1527 | self.calcdisplayattr() | |
1408 |
|
1528 | |||
1409 | def fetch(self, count): |
|
1529 | def fetch(self, count): | |
|
1530 | # Try to fill ``self.items`` with at least ``count`` objects. | |||
1410 | have = len(self.items) |
|
1531 | have = len(self.items) | |
1411 | while not self.exhausted and have < count: |
|
1532 | while not self.exhausted and have < count: | |
1412 | try: |
|
1533 | try: | |
@@ -1419,7 +1540,11 b' if curses is not None:' | |||||
1419 | self.items.append(_BrowserCachedItem(item)) |
|
1540 | self.items.append(_BrowserCachedItem(item)) | |
1420 |
|
1541 | |||
1421 | def calcdisplayattrs(self): |
|
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 | attrnames = set() |
|
1545 | attrnames = set() | |
|
1546 | # If the browser object specifies a fixed list of attributes, | |||
|
1547 | # simply use it. | |||
1423 | if self.attrs: |
|
1548 | if self.attrs: | |
1424 | self.displayattrs = self.attrs |
|
1549 | self.displayattrs = self.attrs | |
1425 | else: |
|
1550 | else: | |
@@ -1432,6 +1557,10 b' if curses is not None:' | |||||
1432 | attrnames.add(attrname) |
|
1557 | attrnames.add(attrname) | |
1433 |
|
1558 | |||
1434 | def getrow(self, i): |
|
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 | row = {} |
|
1564 | row = {} | |
1436 | item = self.items[i].item |
|
1565 | item = self.items[i].item | |
1437 | for attrname in self.displayattrs: |
|
1566 | for attrname in self.displayattrs: | |
@@ -1448,7 +1577,11 b' if curses is not None:' | |||||
1448 | return row |
|
1577 | return row | |
1449 |
|
1578 | |||
1450 | def calcwidths(self): |
|
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 | self.colwidths = {} |
|
1585 | self.colwidths = {} | |
1453 | for row in self.displayrows: |
|
1586 | for row in self.displayrows: | |
1454 | for attrname in self.displayattrs: |
|
1587 | for attrname in self.displayattrs: | |
@@ -1467,7 +1600,8 b' if curses is not None:' | |||||
1467 | self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths) |
|
1600 | self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths) | |
1468 |
|
1601 | |||
1469 | def calcdisplayattr(self): |
|
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 | pos = 0 |
|
1605 | pos = 0 | |
1472 | for attrname in self.displayattrs: |
|
1606 | for attrname in self.displayattrs: | |
1473 | if pos+self.colwidths[attrname] >= self.curx: |
|
1607 | if pos+self.colwidths[attrname] >= self.curx: | |
@@ -1478,6 +1612,10 b' if curses is not None:' | |||||
1478 | self.displayattr = None |
|
1612 | self.displayattr = None | |
1479 |
|
1613 | |||
1480 | def moveto(self, x, y, refresh=False): |
|
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 | olddatastarty = self.datastarty |
|
1619 | olddatastarty = self.datastarty | |
1482 | oldx = self.curx |
|
1620 | oldx = self.curx | |
1483 | oldy = self.cury |
|
1621 | oldy = self.cury | |
@@ -1506,7 +1644,6 b' if curses is not None:' | |||||
1506 | endy = min(self.datastarty+self.mainsizey, len(self.items)) |
|
1644 | endy = min(self.datastarty+self.mainsizey, len(self.items)) | |
1507 | self.displayrows = map(self.getrow, xrange(self.datastarty, endy)) |
|
1645 | self.displayrows = map(self.getrow, xrange(self.datastarty, endy)) | |
1508 | self.calcwidths() |
|
1646 | self.calcwidths() | |
1509 |
|
||||
1510 | # Did we scroll vertically => update displayrows |
|
1647 | # Did we scroll vertically => update displayrows | |
1511 | # and various other attributes |
|
1648 | # and various other attributes | |
1512 | elif self.datastarty != olddatastarty: |
|
1649 | elif self.datastarty != olddatastarty: | |
@@ -1690,27 +1827,61 b' if curses is not None:' | |||||
1690 | } |
|
1827 | } | |
1691 |
|
1828 | |||
1692 | def __init__(self, *attrs): |
|
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 | self.attrs = attrs |
|
1835 | self.attrs = attrs | |
|
1836 | ||||
|
1837 | # Stack of browser levels | |||
1694 | self.levels = [] |
|
1838 | self.levels = [] | |
1695 |
|
|
1839 | # how many colums to scroll (Changes when accelerating) | |
1696 |
self.step |
|
1840 | self.stepx = 1. | |
1697 | self._dobeep = True # Beep on the edges of the data area? |
|
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 | self._colors = {} |
|
1851 | self._colors = {} | |
1699 | self._maxcolor = 1 |
|
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) |
|
1853 | ||
1701 | self._firstheaderline = 0 # Index of first header line |
|
1854 | # How many header lines do we want to paint (the numbers of levels | |
1702 | self.scr = None # curses window |
|
1855 | # we have, but with an upper bound) | |
1703 | self._report = None # report in the footer line |
|
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 | def nextstepx(self, step): |
|
1866 | def nextstepx(self, step): | |
|
1867 | """ | |||
|
1868 | Accelerate horizontally. | |||
|
1869 | """ | |||
1706 | return max(1., min(step*self.acceleratex, |
|
1870 | return max(1., min(step*self.acceleratex, | |
1707 | self.maxspeedx*self.levels[-1].mainsizex)) |
|
1871 | self.maxspeedx*self.levels[-1].mainsizex)) | |
1708 |
|
1872 | |||
1709 | def nextstepy(self, step): |
|
1873 | def nextstepy(self, step): | |
|
1874 | """ | |||
|
1875 | Accelerate vertically. | |||
|
1876 | """ | |||
1710 | return max(1., min(step*self.acceleratey, |
|
1877 | return max(1., min(step*self.acceleratey, | |
1711 | self.maxspeedy*self.levels[-1].mainsizey)) |
|
1878 | self.maxspeedy*self.levels[-1].mainsizey)) | |
1712 |
|
1879 | |||
1713 | def getstyle(self, style): |
|
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 | try: |
|
1885 | try: | |
1715 | return self._colors[style.fg, style.bg] | style.attrs |
|
1886 | return self._colors[style.fg, style.bg] | style.attrs | |
1716 | except KeyError: |
|
1887 | except KeyError: | |
@@ -1722,12 +1893,20 b' if curses is not None:' | |||||
1722 | return c |
|
1893 | return c | |
1723 |
|
1894 | |||
1724 | def addstr(self, y, x, begx, endx, s, style): |
|
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 | s2 = s[max(0, begx-x):max(0, endx-x)] |
|
1900 | s2 = s[max(0, begx-x):max(0, endx-x)] | |
1726 | if s2: |
|
1901 | if s2: | |
1727 | self.scr.addstr(y, max(x, begx), s2, self.getstyle(style)) |
|
1902 | self.scr.addstr(y, max(x, begx), s2, self.getstyle(style)) | |
1728 | return len(s) |
|
1903 | return len(s) | |
1729 |
|
1904 | |||
1730 | def format(self, value): |
|
1905 | def format(self, value): | |
|
1906 | """ | |||
|
1907 | Formats one attribute and returns an ``(alignment, string, style)`` | |||
|
1908 | tuple. | |||
|
1909 | """ | |||
1731 | if value is None: |
|
1910 | if value is None: | |
1732 | return (-1, repr(value), self.style_type_none) |
|
1911 | return (-1, repr(value), self.style_type_none) | |
1733 | elif isinstance(value, str): |
|
1912 | elif isinstance(value, str): | |
@@ -1769,6 +1948,8 b' if curses is not None:' | |||||
1769 | return (-1, repr(value), self.style_default) |
|
1948 | return (-1, repr(value), self.style_default) | |
1770 |
|
1949 | |||
1771 | def _calcheaderlines(self, levels): |
|
1950 | def _calcheaderlines(self, levels): | |
|
1951 | # Calculate how many headerlines do we have to display, if we have | |||
|
1952 | # ``levels`` browser levels | |||
1772 | if levels is None: |
|
1953 | if levels is None: | |
1773 | levels = len(self.levels) |
|
1954 | levels = len(self.levels) | |
1774 | self._headerlines = min(self.maxheaders, levels) |
|
1955 | self._headerlines = min(self.maxheaders, levels) | |
@@ -1782,9 +1963,17 b' if curses is not None:' | |||||
1782 | return Style(style.fg, style.bg, style.attrs | curses.A_BOLD) |
|
1963 | return Style(style.fg, style.bg, style.attrs | curses.A_BOLD) | |
1783 |
|
1964 | |||
1784 | def report(self, msg): |
|
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 | self._report = msg |
|
1970 | self._report = msg | |
1786 |
|
1971 | |||
1787 | def enter(self, item, mode, *attrs): |
|
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 | try: |
|
1977 | try: | |
1789 | iterator = xiter(item, mode) |
|
1978 | iterator = xiter(item, mode) | |
1790 | except (KeyboardInterrupt, SystemExit): |
|
1979 | except (KeyboardInterrupt, SystemExit): | |
@@ -1804,6 +1993,10 b' if curses is not None:' | |||||
1804 | self.levels.append(level) |
|
1993 | self.levels.append(level) | |
1805 |
|
1994 | |||
1806 | def keylabel(self, keycode): |
|
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 | if keycode <= 0xff: |
|
2000 | if keycode <= 0xff: | |
1808 | specialsnames = { |
|
2001 | specialsnames = { | |
1809 | ord("\n"): "RETURN", |
|
2002 | ord("\n"): "RETURN", | |
@@ -1821,6 +2014,9 b' if curses is not None:' | |||||
1821 | return str(keycode) |
|
2014 | return str(keycode) | |
1822 |
|
2015 | |||
1823 | def cmd_help(self): |
|
2016 | def cmd_help(self): | |
|
2017 | """ | |||
|
2018 | The help command | |||
|
2019 | """ | |||
1824 | for level in self.levels: |
|
2020 | for level in self.levels: | |
1825 | if isinstance(level.input, _BrowserHelp): |
|
2021 | if isinstance(level.input, _BrowserHelp): | |
1826 | curses.beep() |
|
2022 | curses.beep() | |
@@ -1830,6 +2026,10 b' if curses is not None:' | |||||
1830 | self.enter(_BrowserHelp(self), "default") |
|
2026 | self.enter(_BrowserHelp(self), "default") | |
1831 |
|
2027 | |||
1832 | def _dodisplay(self, scr): |
|
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 | self.scr = scr |
|
2033 | self.scr = scr | |
1834 | curses.halfdelay(1) |
|
2034 | curses.halfdelay(1) | |
1835 | footery = 2 |
|
2035 | footery = 2 | |
@@ -2144,6 +2344,7 b' if curses is not None:' | |||||
2144 | defaultdisplay = ibrowse |
|
2344 | defaultdisplay = ibrowse | |
2145 | __all__.append("ibrowse") |
|
2345 | __all__.append("ibrowse") | |
2146 | else: |
|
2346 | else: | |
|
2347 | # No curses (probably Windows) => use ``idump`` as the default display. | |||
2147 | defaultdisplay = idump |
|
2348 | defaultdisplay = idump | |
2148 |
|
2349 | |||
2149 |
|
2350 |
General Comments 0
You need to be logged in to leave comments.
Login now