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