##// END OF EJS Templates
Revert r1293: It added little convenience at the cost of extensibility.
walter.doerwald -
Show More
@@ -1,3521 +1,3518 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, footer or table cell of the
49 * When an object ``foo`` is displayed in the header, footer or table cell of the
50 browser ``foo.__xrepr__(mode)`` is called. Mode can be ``"header"`` or
50 browser ``foo.__xrepr__(mode)`` is called. Mode can be ``"header"`` or
51 ``"footer"`` for the header or footer line and ``"cell"`` for a table cell.
51 ``"footer"`` for the header or footer line and ``"cell"`` for a table cell.
52 ``__xrepr__()```must return an iterable (e.g. by being a generator) which
52 ``__xrepr__()```must return an iterable (e.g. by being a generator) which
53 produces the following items: The first item should be a tuple containing
53 produces the following items: The first item should be a tuple containing
54 the alignment (-1 left aligned, 0 centered and 1 right aligned) and whether
54 the alignment (-1 left aligned, 0 centered and 1 right aligned) and whether
55 the complete output must be displayed or if the browser is allowed to stop
55 the complete output must be displayed or if the browser is allowed to stop
56 output after enough text has been produced (e.g. a syntax highlighted text
56 output after enough text has been produced (e.g. a syntax highlighted text
57 line would use ``True``, but for a large data structure (i.e. a nested list,
57 line would use ``True``, but for a large data structure (i.e. a nested list,
58 tuple or dictionary) ``False`` would be used). The other output ``__xrepr__()``
58 tuple or dictionary) ``False`` would be used). The other output ``__xrepr__()``
59 may produce is tuples of ``Style```objects and text (which contain the text
59 may produce is tuples of ``Style```objects and text (which contain the text
60 representation of the object). If ``__xrepr__()`` recursively outputs a data
60 representation of the object). If ``__xrepr__()`` recursively outputs a data
61 structure the function ``xrepr(object, mode)`` can be used and ``"default"``
61 structure the function ``xrepr(object, mode)`` can be used and ``"default"``
62 must be passed as the mode in these calls. This in turn calls the
62 must be passed as the mode in these calls. This in turn calls the
63 ``__xrepr__()`` method on ``object`` (or uses ``repr(object)`` as the string
63 ``__xrepr__()`` method on ``object`` (or uses ``repr(object)`` as the string
64 representation if ``__xrepr__()`` doesn't exist.
64 representation if ``__xrepr__()`` doesn't exist.
65
65
66 * Objects that can be iterated by ``Pipe``s must implement the method
66 * Objects that can be iterated by ``Pipe``s must implement the method
67 ``__xiter__(self, mode)``. ``mode`` can take the following values:
67 ``__xiter__(self, mode)``. ``mode`` can take the following values:
68
68
69 - ``"default"``: This is the default value and ist always used by pipeline
69 - ``"default"``: This is the default value and ist always used by pipeline
70 expressions. Other values are only used in the browser.
70 expressions. Other values are only used in the browser.
71 - ``None``: This value is passed by the browser. The object must return an
71 - ``None``: This value is passed by the browser. The object must return an
72 iterable of ``XMode`` objects describing all modes supported by the object.
72 iterable of ``XMode`` objects describing all modes supported by the object.
73 (This should never include ``"default"`` or ``None``).
73 (This should never include ``"default"`` or ``None``).
74 - Any other value that the object supports.
74 - Any other value that the object supports.
75
75
76 The global function ``xiter()`` can be called to get such an iterator. If
76 The global function ``xiter()`` can be called to get such an iterator. If
77 the method ``_xiter__`` isn't implemented, ``xiter()`` falls back to
77 the method ``_xiter__`` isn't implemented, ``xiter()`` falls back to
78 ``__iter__``. In addition to that, dictionaries and modules receive special
78 ``__iter__``. In addition to that, dictionaries and modules receive special
79 treatment (returning an iterator over ``(key, value)`` pairs). This makes it
79 treatment (returning an iterator over ``(key, value)`` pairs). This makes it
80 possible to use dictionaries and modules in pipeline expressions, for example:
80 possible to use dictionaries and modules in pipeline expressions, for example:
81
81
82 >>> import sys
82 >>> import sys
83 >>> sys | ifilter("isinstance(value, int)") | idump
83 >>> sys | ifilter("isinstance(value, int)") | idump
84 key |value
84 key |value
85 api_version| 1012
85 api_version| 1012
86 dllhandle | 503316480
86 dllhandle | 503316480
87 hexversion | 33817328
87 hexversion | 33817328
88 maxint |2147483647
88 maxint |2147483647
89 maxunicode | 65535
89 maxunicode | 65535
90 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
90 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
91 ...
91 ...
92
92
93 Note: The expression strings passed to ``ifilter()`` and ``isort()`` can
93 Note: The expression strings passed to ``ifilter()`` and ``isort()`` can
94 refer to the object to be filtered or sorted via the variable ``_`` and to any
94 refer to the object to be filtered or sorted via the variable ``_`` and to any
95 of the attributes of the object, i.e.:
95 of the attributes of the object, i.e.:
96
96
97 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
97 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
98
98
99 does the same as
99 does the same as
100
100
101 >>> sys.modules | ifilter("value is not None") | isort("key.lower()")
101 >>> sys.modules | ifilter("value is not None") | isort("key.lower()")
102
102
103 In addition to expression strings, it's possible to pass callables (taking
103 In addition to expression strings, it's possible to pass callables (taking
104 the object as an argument) to ``ifilter()``, ``isort()`` and ``ieval()``:
104 the object as an argument) to ``ifilter()``, ``isort()`` and ``ieval()``:
105
105
106 >>> sys | ifilter(lambda _:isinstance(_.value, int)) \
106 >>> sys | ifilter(lambda _:isinstance(_.value, int)) \
107 ... | ieval(lambda _: (_.key, hex(_.value))) | idump
107 ... | ieval(lambda _: (_.key, hex(_.value))) | idump
108 0 |1
108 0 |1
109 api_version|0x3f4
109 api_version|0x3f4
110 dllhandle |0x1e000000
110 dllhandle |0x1e000000
111 hexversion |0x20402f0
111 hexversion |0x20402f0
112 maxint |0x7fffffff
112 maxint |0x7fffffff
113 maxunicode |0xffff
113 maxunicode |0xffff
114 """
114 """
115
115
116 import sys, os, os.path, stat, glob, new, csv, datetime, types
116 import sys, os, os.path, stat, glob, new, csv, datetime, types
117 import textwrap, itertools, mimetypes
117 import textwrap, itertools, mimetypes
118
118
119 try: # Python 2.3 compatibility
119 try: # Python 2.3 compatibility
120 import collections
120 import collections
121 except ImportError:
121 except ImportError:
122 deque = list
122 deque = list
123 else:
123 else:
124 deque = collections.deque
124 deque = collections.deque
125
125
126 try: # Python 2.3 compatibility
126 try: # Python 2.3 compatibility
127 set
127 set
128 except NameError:
128 except NameError:
129 import sets
129 import sets
130 set = sets.Set
130 set = sets.Set
131
131
132 try: # Python 2.3 compatibility
132 try: # Python 2.3 compatibility
133 sorted
133 sorted
134 except NameError:
134 except NameError:
135 def sorted(iterator, key=None, reverse=False):
135 def sorted(iterator, key=None, reverse=False):
136 items = list(iterator)
136 items = list(iterator)
137 if key is not None:
137 if key is not None:
138 items.sort(lambda i1, i2: cmp(key(i1), key(i2)))
138 items.sort(lambda i1, i2: cmp(key(i1), key(i2)))
139 else:
139 else:
140 items.sort()
140 items.sort()
141 if reverse:
141 if reverse:
142 items.reverse()
142 items.reverse()
143 return items
143 return items
144
144
145 try:
145 try:
146 import pwd
146 import pwd
147 except ImportError:
147 except ImportError:
148 pwd = None
148 pwd = None
149
149
150 try:
150 try:
151 import grp
151 import grp
152 except ImportError:
152 except ImportError:
153 grp = None
153 grp = None
154
154
155 try:
155 try:
156 import curses
156 import curses
157 except ImportError:
157 except ImportError:
158 curses = None
158 curses = None
159
159
160 import path
160 import path
161 try:
161 try:
162 from IPython import genutils
162 from IPython import genutils
163 except ImportError:
163 except ImportError:
164 pass
164 pass
165
165
166
166
167 __all__ = [
167 __all__ = [
168 "ifile", "ils", "iglob", "iwalk", "ipwdentry", "ipwd", "igrpentry", "igrp",
168 "ifile", "ils", "iglob", "iwalk", "ipwdentry", "ipwd", "igrpentry", "igrp",
169 "icsv", "ix", "ichain", "isort", "ifilter", "ieval", "ienum", "ienv",
169 "icsv", "ix", "ichain", "isort", "ifilter", "ieval", "ienum", "ienv",
170 "idump", "iless"
170 "idump", "iless"
171 ]
171 ]
172
172
173
173
174 os.stat_float_times(True) # enable microseconds
174 os.stat_float_times(True) # enable microseconds
175
175
176
176
177 class _AttrNamespace(object):
177 class _AttrNamespace(object):
178 """
178 """
179 Internal helper class that is used for providing a namespace for evaluating
179 Internal helper class that is used for providing a namespace for evaluating
180 expressions containg attribute names of an object.
180 expressions containg attribute names of an object.
181 """
181 """
182 def __init__(self, wrapped):
182 def __init__(self, wrapped):
183 self.wrapped = wrapped
183 self.wrapped = wrapped
184
184
185 def __getitem__(self, name):
185 def __getitem__(self, name):
186 if name == "_":
186 if name == "_":
187 return self.wrapped
187 return self.wrapped
188 try:
188 try:
189 return getattr(self.wrapped, name)
189 return getattr(self.wrapped, name)
190 except AttributeError:
190 except AttributeError:
191 raise KeyError(name)
191 raise KeyError(name)
192
192
193 # Python 2.3 compatibility
193 # Python 2.3 compatibility
194 # use eval workaround to find out which names are used in the
194 # use eval workaround to find out which names are used in the
195 # eval string and put them into the locals. This works for most
195 # eval string and put them into the locals. This works for most
196 # normal uses case, bizarre ones like accessing the locals()
196 # normal uses case, bizarre ones like accessing the locals()
197 # will fail
197 # will fail
198 try:
198 try:
199 eval("_", None, _AttrNamespace(None))
199 eval("_", None, _AttrNamespace(None))
200 except TypeError:
200 except TypeError:
201 real_eval = eval
201 real_eval = eval
202 def eval(codestring, _globals, _locals):
202 def eval(codestring, _globals, _locals):
203 """
203 """
204 eval(source[, globals[, locals]]) -> value
204 eval(source[, globals[, locals]]) -> value
205
205
206 Evaluate the source in the context of globals and locals.
206 Evaluate the source in the context of globals and locals.
207 The source may be a string representing a Python expression
207 The source may be a string representing a Python expression
208 or a code object as returned by compile().
208 or a code object as returned by compile().
209 The globals must be a dictionary and locals can be any mappping.
209 The globals must be a dictionary and locals can be any mappping.
210
210
211 This function is a workaround for the shortcomings of
211 This function is a workaround for the shortcomings of
212 Python 2.3's eval.
212 Python 2.3's eval.
213 """
213 """
214
214
215 code = compile(codestring, "_eval", "eval")
215 code = compile(codestring, "_eval", "eval")
216 newlocals = {}
216 newlocals = {}
217 for name in code.co_names:
217 for name in code.co_names:
218 try:
218 try:
219 newlocals[name] = _locals[name]
219 newlocals[name] = _locals[name]
220 except KeyError:
220 except KeyError:
221 pass
221 pass
222 return real_eval(code, _globals, newlocals)
222 return real_eval(code, _globals, newlocals)
223
223
224
224
225 _default = object()
225 _default = object()
226
226
227 def item(iterator, index, default=_default):
227 def item(iterator, index, default=_default):
228 """
228 """
229 Return the ``index``th item from the iterator ``iterator``.
229 Return the ``index``th item from the iterator ``iterator``.
230 ``index`` must be an integer (negative integers are relative to the
230 ``index`` must be an integer (negative integers are relative to the
231 end (i.e. the last item produced by the iterator)).
231 end (i.e. the last item produced by the iterator)).
232
232
233 If ``default`` is given, this will be the default value when
233 If ``default`` is given, this will be the default value when
234 the iterator doesn't contain an item at this position. Otherwise an
234 the iterator doesn't contain an item at this position. Otherwise an
235 ``IndexError`` will be raised.
235 ``IndexError`` will be raised.
236
236
237 Note that using this function will partially or totally exhaust the
237 Note that using this function will partially or totally exhaust the
238 iterator.
238 iterator.
239 """
239 """
240 i = index
240 i = index
241 if i>=0:
241 if i>=0:
242 for item in iterator:
242 for item in iterator:
243 if not i:
243 if not i:
244 return item
244 return item
245 i -= 1
245 i -= 1
246 else:
246 else:
247 i = -index
247 i = -index
248 cache = deque()
248 cache = deque()
249 for item in iterator:
249 for item in iterator:
250 cache.append(item)
250 cache.append(item)
251 if len(cache)>i:
251 if len(cache)>i:
252 cache.popleft()
252 cache.popleft()
253 if len(cache)==i:
253 if len(cache)==i:
254 return cache.popleft()
254 return cache.popleft()
255 if default is _default:
255 if default is _default:
256 raise IndexError(index)
256 raise IndexError(index)
257 else:
257 else:
258 return default
258 return default
259
259
260
260
261 class Table(object):
261 class Table(object):
262 """
262 """
263 A ``Table`` is an object that produces items (just like a normal Python
263 A ``Table`` is an object that produces items (just like a normal Python
264 iterator/generator does) and can be used as the first object in a pipeline
264 iterator/generator does) and can be used as the first object in a pipeline
265 expression. The displayhook will open the default browser for such an object
265 expression. The displayhook will open the default browser for such an object
266 (instead of simply printing the ``repr()`` result).
266 (instead of simply printing the ``repr()`` result).
267 """
267 """
268
268
269 # We want to support ``foo`` and ``foo()`` in pipeline expression:
269 # We want to support ``foo`` and ``foo()`` in pipeline expression:
270 # So we implement the required operators (``|`` and ``+``) in the metaclass,
270 # So we implement the required operators (``|`` and ``+``) in the metaclass,
271 # instantiate the class and forward the operator to the instance
271 # instantiate the class and forward the operator to the instance
272 class __metaclass__(type):
272 class __metaclass__(type):
273 def __iter__(self):
273 def __iter__(self):
274 return iter(self())
274 return iter(self())
275
275
276 def __or__(self, other):
276 def __or__(self, other):
277 return self() | other
277 return self() | other
278
278
279 def __add__(self, other):
279 def __add__(self, other):
280 return self() + other
280 return self() + other
281
281
282 def __radd__(self, other):
282 def __radd__(self, other):
283 return other + self()
283 return other + self()
284
284
285 def __getitem__(self, index):
285 def __getitem__(self, index):
286 return self()[index]
286 return self()[index]
287
287
288 def __getitem__(self, index):
288 def __getitem__(self, index):
289 return item(self, index)
289 return item(self, index)
290
290
291 def __contains__(self, item):
291 def __contains__(self, item):
292 for haveitem in self:
292 for haveitem in self:
293 if item == haveitem:
293 if item == haveitem:
294 return True
294 return True
295 return False
295 return False
296
296
297 def __or__(self, other):
297 def __or__(self, other):
298 # autoinstantiate right hand side
298 # autoinstantiate right hand side
299 if isinstance(other, type) and issubclass(other, (Table, Display)):
299 if isinstance(other, type) and issubclass(other, (Table, Display)):
300 other = other()
300 other = other()
301 # treat simple strings and functions as ``ieval`` instances
301 # treat simple strings and functions as ``ieval`` instances
302 elif not isinstance(other, Display) and not isinstance(other, Table):
302 elif not isinstance(other, Display) and not isinstance(other, Table):
303 other = ieval(other)
303 other = ieval(other)
304 # forward operations to the right hand side
304 # forward operations to the right hand side
305 return other.__ror__(self)
305 return other.__ror__(self)
306
306
307 def __add__(self, other):
307 def __add__(self, other):
308 # autoinstantiate right hand side
308 # autoinstantiate right hand side
309 if isinstance(other, type) and issubclass(other, Table):
309 if isinstance(other, type) and issubclass(other, Table):
310 other = other()
310 other = other()
311 return ichain(self, other)
311 return ichain(self, other)
312
312
313 def __radd__(self, other):
313 def __radd__(self, other):
314 # autoinstantiate left hand side
314 # autoinstantiate left hand side
315 if isinstance(other, type) and issubclass(other, Table):
315 if isinstance(other, type) and issubclass(other, Table):
316 other = other()
316 other = other()
317 return ichain(other, self)
317 return ichain(other, self)
318
318
319 def __iter__(self):
319 def __iter__(self):
320 return xiter(self, "default")
320 return xiter(self, "default")
321
321
322 def __xiter__(self, mode="default"):
323 raise NotImplementedError
324
325
322
326 class Pipe(Table):
323 class Pipe(Table):
327 """
324 """
328 A ``Pipe`` is an object that can be used in a pipeline expression. It
325 A ``Pipe`` is an object that can be used in a pipeline expression. It
329 processes the objects it gets from its input ``Table``/``Pipe``. Note that
326 processes the objects it gets from its input ``Table``/``Pipe``. Note that
330 a ``Pipe`` object can't be used as the first object in a pipeline
327 a ``Pipe`` object can't be used as the first object in a pipeline
331 expression, as it doesn't produces items itself.
328 expression, as it doesn't produces items itself.
332 """
329 """
333 class __metaclass__(Table.__metaclass__):
330 class __metaclass__(Table.__metaclass__):
334 def __ror__(self, input):
331 def __ror__(self, input):
335 return input | self()
332 return input | self()
336
333
337 def __ror__(self, input):
334 def __ror__(self, input):
338 # autoinstantiate left hand side
335 # autoinstantiate left hand side
339 if isinstance(input, type) and issubclass(input, Table):
336 if isinstance(input, type) and issubclass(input, Table):
340 input = input()
337 input = input()
341 self.input = input
338 self.input = input
342 return self
339 return self
343
340
344
341
345 def _getattr(obj, name, default=_default):
342 def _getattr(obj, name, default=_default):
346 """
343 """
347 Internal helper for getting an attribute of an item. If ``name`` is ``None``
344 Internal helper for getting an attribute of an item. If ``name`` is ``None``
348 return the object itself. If ``name`` is an integer, use ``__getitem__``
345 return the object itself. If ``name`` is an integer, use ``__getitem__``
349 instead. If the attribute or item does not exist, return ``default``.
346 instead. If the attribute or item does not exist, return ``default``.
350 """
347 """
351 if name is None:
348 if name is None:
352 return obj
349 return obj
353 elif isinstance(name, basestring):
350 elif isinstance(name, basestring):
354 if name.endswith("()"):
351 if name.endswith("()"):
355 return getattr(obj, name[:-2], default)()
352 return getattr(obj, name[:-2], default)()
356 else:
353 else:
357 return getattr(obj, name, default)
354 return getattr(obj, name, default)
358 elif callable(name):
355 elif callable(name):
359 try:
356 try:
360 return name(obj)
357 return name(obj)
361 except AttributeError:
358 except AttributeError:
362 return default
359 return default
363 else:
360 else:
364 try:
361 try:
365 return obj[name]
362 return obj[name]
366 except IndexError:
363 except IndexError:
367 return default
364 return default
368
365
369
366
370 def _attrname(name):
367 def _attrname(name):
371 """
368 """
372 Internal helper that gives a proper name for the attribute ``name``
369 Internal helper that gives a proper name for the attribute ``name``
373 (which might be ``None`` or an ``int``).
370 (which might be ``None`` or an ``int``).
374 """
371 """
375 if name is None:
372 if name is None:
376 return "_"
373 return "_"
377 elif isinstance(name, basestring):
374 elif isinstance(name, basestring):
378 return name
375 return name
379 elif callable(name):
376 elif callable(name):
380 return getattr(name, "__xname__", name.__name__)
377 return getattr(name, "__xname__", name.__name__)
381 else:
378 else:
382 return str(name)
379 return str(name)
383
380
384
381
385 COLOR_BLACK = 0
382 COLOR_BLACK = 0
386 COLOR_RED = 1
383 COLOR_RED = 1
387 COLOR_GREEN = 2
384 COLOR_GREEN = 2
388 COLOR_YELLOW = 3
385 COLOR_YELLOW = 3
389 COLOR_BLUE = 4
386 COLOR_BLUE = 4
390 COLOR_MAGENTA = 5
387 COLOR_MAGENTA = 5
391 COLOR_CYAN = 6
388 COLOR_CYAN = 6
392 COLOR_WHITE = 7
389 COLOR_WHITE = 7
393
390
394 A_BLINK = 1<<0 # Blinking text
391 A_BLINK = 1<<0 # Blinking text
395 A_BOLD = 1<<1 # Extra bright or bold text
392 A_BOLD = 1<<1 # Extra bright or bold text
396 A_DIM = 1<<2 # Half bright text
393 A_DIM = 1<<2 # Half bright text
397 A_REVERSE = 1<<3 # Reverse-video text
394 A_REVERSE = 1<<3 # Reverse-video text
398 A_STANDOUT = 1<<4 # The best highlighting mode available
395 A_STANDOUT = 1<<4 # The best highlighting mode available
399 A_UNDERLINE = 1<<5 # Underlined text
396 A_UNDERLINE = 1<<5 # Underlined text
400
397
401
398
402 class Style(object):
399 class Style(object):
403 """
400 """
404 Store foreground color, background color and attribute (bold, underlined
401 Store foreground color, background color and attribute (bold, underlined
405 etc.).
402 etc.).
406 """
403 """
407 __slots__ = ("fg", "bg", "attrs")
404 __slots__ = ("fg", "bg", "attrs")
408
405
409 COLORNAMES = {
406 COLORNAMES = {
410 "black": COLOR_BLACK,
407 "black": COLOR_BLACK,
411 "red": COLOR_RED,
408 "red": COLOR_RED,
412 "green": COLOR_GREEN,
409 "green": COLOR_GREEN,
413 "yellow": COLOR_YELLOW,
410 "yellow": COLOR_YELLOW,
414 "blue": COLOR_BLUE,
411 "blue": COLOR_BLUE,
415 "magenta": COLOR_MAGENTA,
412 "magenta": COLOR_MAGENTA,
416 "cyan": COLOR_CYAN,
413 "cyan": COLOR_CYAN,
417 "white": COLOR_WHITE,
414 "white": COLOR_WHITE,
418 }
415 }
419 ATTRNAMES = {
416 ATTRNAMES = {
420 "blink": A_BLINK,
417 "blink": A_BLINK,
421 "bold": A_BOLD,
418 "bold": A_BOLD,
422 "dim": A_DIM,
419 "dim": A_DIM,
423 "reverse": A_REVERSE,
420 "reverse": A_REVERSE,
424 "standout": A_STANDOUT,
421 "standout": A_STANDOUT,
425 "underline": A_UNDERLINE,
422 "underline": A_UNDERLINE,
426 }
423 }
427
424
428 def __init__(self, fg, bg, attrs=0):
425 def __init__(self, fg, bg, attrs=0):
429 """
426 """
430 Create a ``Style`` object with ``fg`` as the foreground color,
427 Create a ``Style`` object with ``fg`` as the foreground color,
431 ``bg`` as the background color and ``attrs`` as the attributes.
428 ``bg`` as the background color and ``attrs`` as the attributes.
432
429
433 Examples:
430 Examples:
434
431
435 >>> Style(COLOR_RED, COLOR_BLACK)
432 >>> Style(COLOR_RED, COLOR_BLACK)
436 >>> Style(COLOR_YELLOW, COLOR_BLUE, A_BOLD|A_UNDERLINE)
433 >>> Style(COLOR_YELLOW, COLOR_BLUE, A_BOLD|A_UNDERLINE)
437 """
434 """
438 self.fg = fg
435 self.fg = fg
439 self.bg = bg
436 self.bg = bg
440 self.attrs = attrs
437 self.attrs = attrs
441
438
442 def __call__(self, *args):
439 def __call__(self, *args):
443 text = Text()
440 text = Text()
444 for arg in args:
441 for arg in args:
445 if isinstance(arg, Text):
442 if isinstance(arg, Text):
446 text.extend(arg)
443 text.extend(arg)
447 else:
444 else:
448 text.append((self, arg))
445 text.append((self, arg))
449 return text
446 return text
450
447
451 def __eq__(self, other):
448 def __eq__(self, other):
452 return self.fg == other.fg and self.bg == other.bg and self.attrs == other.attrs
449 return self.fg == other.fg and self.bg == other.bg and self.attrs == other.attrs
453
450
454 def __neq__(self, other):
451 def __neq__(self, other):
455 return self.fg != other.fg or self.bg != other.bg or self.attrs != other.attrs
452 return self.fg != other.fg or self.bg != other.bg or self.attrs != other.attrs
456
453
457 def __repr__(self):
454 def __repr__(self):
458 color2name = ("black", "red", "green", "yellow", "blue", "magenta", "cyan", "white")
455 color2name = ("black", "red", "green", "yellow", "blue", "magenta", "cyan", "white")
459 attrs2name = ("blink", "bold", "dim", "reverse", "standout", "underline")
456 attrs2name = ("blink", "bold", "dim", "reverse", "standout", "underline")
460
457
461 return "<%s fg=%s bg=%s attrs=%s>" % (
458 return "<%s fg=%s bg=%s attrs=%s>" % (
462 self.__class__.__name__, color2name[self.fg], color2name[self.bg],
459 self.__class__.__name__, color2name[self.fg], color2name[self.bg],
463 "|".join([attrs2name[b] for b in xrange(6) if self.attrs&(1<<b)]) or 0)
460 "|".join([attrs2name[b] for b in xrange(6) if self.attrs&(1<<b)]) or 0)
464
461
465 def fromstr(cls, value):
462 def fromstr(cls, value):
466 """
463 """
467 Create a ``Style`` object from a string. The format looks like this:
464 Create a ``Style`` object from a string. The format looks like this:
468 ``"red:black:bold|blink"``.
465 ``"red:black:bold|blink"``.
469 """
466 """
470 # defaults
467 # defaults
471 fg = COLOR_WHITE
468 fg = COLOR_WHITE
472 bg = COLOR_BLACK
469 bg = COLOR_BLACK
473 attrs = 0
470 attrs = 0
474
471
475 parts = value.split(":")
472 parts = value.split(":")
476 if len(parts) > 0:
473 if len(parts) > 0:
477 fg = cls.COLORNAMES[parts[0].lower()]
474 fg = cls.COLORNAMES[parts[0].lower()]
478 if len(parts) > 1:
475 if len(parts) > 1:
479 bg = cls.COLORNAMES[parts[1].lower()]
476 bg = cls.COLORNAMES[parts[1].lower()]
480 if len(parts) > 2:
477 if len(parts) > 2:
481 for strattr in parts[2].split("|"):
478 for strattr in parts[2].split("|"):
482 attrs |= cls.ATTRNAMES[strattr.lower()]
479 attrs |= cls.ATTRNAMES[strattr.lower()]
483 return cls(fg, bg, attrs)
480 return cls(fg, bg, attrs)
484 fromstr = classmethod(fromstr)
481 fromstr = classmethod(fromstr)
485
482
486 def fromenv(cls, name, default):
483 def fromenv(cls, name, default):
487 """
484 """
488 Create a ``Style`` from an environment variable named ``name``
485 Create a ``Style`` from an environment variable named ``name``
489 (using ``default`` if the environment variable doesn't exist).
486 (using ``default`` if the environment variable doesn't exist).
490 """
487 """
491 return cls.fromstr(os.environ.get(name, default))
488 return cls.fromstr(os.environ.get(name, default))
492 fromenv = classmethod(fromenv)
489 fromenv = classmethod(fromenv)
493
490
494
491
495 def switchstyle(s1, s2):
492 def switchstyle(s1, s2):
496 """
493 """
497 Return the ANSI escape sequence needed to switch from style ``s1`` to
494 Return the ANSI escape sequence needed to switch from style ``s1`` to
498 style ``s2``.
495 style ``s2``.
499 """
496 """
500 attrmask = (A_BLINK|A_BOLD|A_UNDERLINE|A_REVERSE)
497 attrmask = (A_BLINK|A_BOLD|A_UNDERLINE|A_REVERSE)
501 a1 = s1.attrs & attrmask
498 a1 = s1.attrs & attrmask
502 a2 = s2.attrs & attrmask
499 a2 = s2.attrs & attrmask
503
500
504 args = []
501 args = []
505 if s1 != s2:
502 if s1 != s2:
506 # do we have to get rid of the bold/underline/blink bit?
503 # do we have to get rid of the bold/underline/blink bit?
507 # (can only be done by a reset)
504 # (can only be done by a reset)
508 # use reset when our target color is the default color
505 # use reset when our target color is the default color
509 # (this is shorter than 37;40)
506 # (this is shorter than 37;40)
510 if (a1 & ~a2 or s2==style_default):
507 if (a1 & ~a2 or s2==style_default):
511 args.append("0")
508 args.append("0")
512 s1 = style_default
509 s1 = style_default
513 a1 = 0
510 a1 = 0
514
511
515 # now we know that old and new color have the same boldness,
512 # now we know that old and new color have the same boldness,
516 # or the new color is bold and the old isn't,
513 # or the new color is bold and the old isn't,
517 # i.e. we only might have to switch bold on, not off
514 # i.e. we only might have to switch bold on, not off
518 if not (a1 & A_BOLD) and (a2 & A_BOLD):
515 if not (a1 & A_BOLD) and (a2 & A_BOLD):
519 args.append("1")
516 args.append("1")
520
517
521 # Fix underline
518 # Fix underline
522 if not (a1 & A_UNDERLINE) and (a2 & A_UNDERLINE):
519 if not (a1 & A_UNDERLINE) and (a2 & A_UNDERLINE):
523 args.append("4")
520 args.append("4")
524
521
525 # Fix blink
522 # Fix blink
526 if not (a1 & A_BLINK) and (a2 & A_BLINK):
523 if not (a1 & A_BLINK) and (a2 & A_BLINK):
527 args.append("5")
524 args.append("5")
528
525
529 # Fix reverse
526 # Fix reverse
530 if not (a1 & A_REVERSE) and (a2 & A_REVERSE):
527 if not (a1 & A_REVERSE) and (a2 & A_REVERSE):
531 args.append("7")
528 args.append("7")
532
529
533 # Fix foreground color
530 # Fix foreground color
534 if s1.fg != s2.fg:
531 if s1.fg != s2.fg:
535 args.append("3%d" % s2.fg)
532 args.append("3%d" % s2.fg)
536
533
537 # Finally fix the background color
534 # Finally fix the background color
538 if s1.bg != s2.bg:
535 if s1.bg != s2.bg:
539 args.append("4%d" % s2.bg)
536 args.append("4%d" % s2.bg)
540
537
541 if args:
538 if args:
542 return "\033[%sm" % ";".join(args)
539 return "\033[%sm" % ";".join(args)
543 return ""
540 return ""
544
541
545
542
546 class Text(list):
543 class Text(list):
547 """
544 """
548 A colored string. A ``Text`` object is a sequence, the sequence
545 A colored string. A ``Text`` object is a sequence, the sequence
549 items will be ``(style, string)`` tuples.
546 items will be ``(style, string)`` tuples.
550 """
547 """
551
548
552 def __init__(self, *args):
549 def __init__(self, *args):
553 list.__init__(self)
550 list.__init__(self)
554 self.append(*args)
551 self.append(*args)
555
552
556 def __repr__(self):
553 def __repr__(self):
557 return "%s.%s(%s)" % (
554 return "%s.%s(%s)" % (
558 self.__class__.__module__, self.__class__.__name__,
555 self.__class__.__module__, self.__class__.__name__,
559 list.__repr__(self)[1:-1])
556 list.__repr__(self)[1:-1])
560
557
561 def append(self, *args):
558 def append(self, *args):
562 for arg in args:
559 for arg in args:
563 if isinstance(arg, Text):
560 if isinstance(arg, Text):
564 self.extend(arg)
561 self.extend(arg)
565 elif isinstance(arg, tuple): # must be (style, string)
562 elif isinstance(arg, tuple): # must be (style, string)
566 list.append(self, arg)
563 list.append(self, arg)
567 elif isinstance(arg, unicode):
564 elif isinstance(arg, unicode):
568 list.append(self, (style_default, arg))
565 list.append(self, (style_default, arg))
569 else:
566 else:
570 list.append(self, (style_default, str(arg)))
567 list.append(self, (style_default, str(arg)))
571
568
572 def insert(self, index, *args):
569 def insert(self, index, *args):
573 self[index:index] = Text(*args)
570 self[index:index] = Text(*args)
574
571
575 def __add__(self, other):
572 def __add__(self, other):
576 new = Text()
573 new = Text()
577 new.append(self)
574 new.append(self)
578 new.append(other)
575 new.append(other)
579 return new
576 return new
580
577
581 def __iadd__(self, other):
578 def __iadd__(self, other):
582 self.append(other)
579 self.append(other)
583 return self
580 return self
584
581
585 def format(self, styled=True):
582 def format(self, styled=True):
586 """
583 """
587 This generator yields the strings that will make up the final
584 This generator yields the strings that will make up the final
588 colorized string.
585 colorized string.
589 """
586 """
590 if styled:
587 if styled:
591 oldstyle = style_default
588 oldstyle = style_default
592 for (style, string) in self:
589 for (style, string) in self:
593 if not isinstance(style, (int, long)):
590 if not isinstance(style, (int, long)):
594 switch = switchstyle(oldstyle, style)
591 switch = switchstyle(oldstyle, style)
595 if switch:
592 if switch:
596 yield switch
593 yield switch
597 if string:
594 if string:
598 yield string
595 yield string
599 oldstyle = style
596 oldstyle = style
600 switch = switchstyle(oldstyle, style_default)
597 switch = switchstyle(oldstyle, style_default)
601 if switch:
598 if switch:
602 yield switch
599 yield switch
603 else:
600 else:
604 for (style, string) in self:
601 for (style, string) in self:
605 if not isinstance(style, (int, long)):
602 if not isinstance(style, (int, long)):
606 yield string
603 yield string
607
604
608 def string(self, styled=True):
605 def string(self, styled=True):
609 """
606 """
610 Return the resulting string (with escape sequences, if ``styled``
607 Return the resulting string (with escape sequences, if ``styled``
611 is true).
608 is true).
612 """
609 """
613 return "".join(self.format(styled))
610 return "".join(self.format(styled))
614
611
615 def __str__(self):
612 def __str__(self):
616 """
613 """
617 Return the resulting string with ANSI escape sequences.
614 Return the resulting string with ANSI escape sequences.
618 """
615 """
619 return self.string(False)
616 return self.string(False)
620
617
621 def write(self, stream, styled=True):
618 def write(self, stream, styled=True):
622 for part in self.format(styled):
619 for part in self.format(styled):
623 stream.write(part)
620 stream.write(part)
624
621
625 def __xrepr__(self, mode="default"):
622 def __xrepr__(self, mode="default"):
626 yield (-1, True)
623 yield (-1, True)
627 for info in self:
624 for info in self:
628 yield info
625 yield info
629
626
630
627
631 if curses is not None:
628 if curses is not None:
632 # This is probably just range(8)
629 # This is probably just range(8)
633 COLOR2CURSES = [
630 COLOR2CURSES = [
634 COLOR_BLACK,
631 COLOR_BLACK,
635 COLOR_RED,
632 COLOR_RED,
636 COLOR_GREEN,
633 COLOR_GREEN,
637 COLOR_YELLOW,
634 COLOR_YELLOW,
638 COLOR_BLUE,
635 COLOR_BLUE,
639 COLOR_MAGENTA,
636 COLOR_MAGENTA,
640 COLOR_CYAN,
637 COLOR_CYAN,
641 COLOR_WHITE,
638 COLOR_WHITE,
642 ]
639 ]
643
640
644 A2CURSES = {
641 A2CURSES = {
645 A_BLINK: curses.A_BLINK,
642 A_BLINK: curses.A_BLINK,
646 A_BOLD: curses.A_BOLD,
643 A_BOLD: curses.A_BOLD,
647 A_DIM: curses.A_DIM,
644 A_DIM: curses.A_DIM,
648 A_REVERSE: curses.A_REVERSE,
645 A_REVERSE: curses.A_REVERSE,
649 A_STANDOUT: curses.A_STANDOUT,
646 A_STANDOUT: curses.A_STANDOUT,
650 A_UNDERLINE: curses.A_UNDERLINE,
647 A_UNDERLINE: curses.A_UNDERLINE,
651 }
648 }
652
649
653
650
654 # default style
651 # default style
655 style_default = Style(COLOR_WHITE, COLOR_BLACK)
652 style_default = Style(COLOR_WHITE, COLOR_BLACK)
656
653
657 # Styles for datatypes
654 # Styles for datatypes
658 style_type_none = Style(COLOR_MAGENTA, COLOR_BLACK)
655 style_type_none = Style(COLOR_MAGENTA, COLOR_BLACK)
659 style_type_bool = Style(COLOR_MAGENTA, COLOR_BLACK)
656 style_type_bool = Style(COLOR_MAGENTA, COLOR_BLACK)
660 style_type_number = Style(COLOR_YELLOW, COLOR_BLACK)
657 style_type_number = Style(COLOR_YELLOW, COLOR_BLACK)
661 style_type_datetime = Style(COLOR_MAGENTA, COLOR_BLACK)
658 style_type_datetime = Style(COLOR_MAGENTA, COLOR_BLACK)
662
659
663 # Style for URLs and file/directory names
660 # Style for URLs and file/directory names
664 style_url = Style(COLOR_GREEN, COLOR_BLACK)
661 style_url = Style(COLOR_GREEN, COLOR_BLACK)
665 style_dir = Style(COLOR_CYAN, COLOR_BLACK)
662 style_dir = Style(COLOR_CYAN, COLOR_BLACK)
666 style_file = Style(COLOR_GREEN, COLOR_BLACK)
663 style_file = Style(COLOR_GREEN, COLOR_BLACK)
667
664
668 # Style for ellipsis (when an output has been shortened
665 # Style for ellipsis (when an output has been shortened
669 style_ellisis = Style(COLOR_RED, COLOR_BLACK)
666 style_ellisis = Style(COLOR_RED, COLOR_BLACK)
670
667
671 # Style for displaying exceptions
668 # Style for displaying exceptions
672 style_error = Style(COLOR_RED, COLOR_BLACK)
669 style_error = Style(COLOR_RED, COLOR_BLACK)
673
670
674 # Style for displaying non-existing attributes
671 # Style for displaying non-existing attributes
675 style_nodata = Style(COLOR_RED, COLOR_BLACK)
672 style_nodata = Style(COLOR_RED, COLOR_BLACK)
676
673
677
674
678 def xrepr(item, mode):
675 def xrepr(item, mode):
679 try:
676 try:
680 func = item.__xrepr__
677 func = item.__xrepr__
681 except AttributeError:
678 except AttributeError:
682 pass
679 pass
683 else:
680 else:
684 try:
681 try:
685 for x in func(mode):
682 for x in func(mode):
686 yield x
683 yield x
687 except (KeyboardInterrupt, SystemExit):
684 except (KeyboardInterrupt, SystemExit):
688 raise
685 raise
689 except Exception:
686 except Exception:
690 yield (-1, True)
687 yield (-1, True)
691 yield (style_default, repr(item))
688 yield (style_default, repr(item))
692 return
689 return
693 if item is None:
690 if item is None:
694 yield (-1, True)
691 yield (-1, True)
695 yield (style_type_none, repr(item))
692 yield (style_type_none, repr(item))
696 elif isinstance(item, bool):
693 elif isinstance(item, bool):
697 yield (-1, True)
694 yield (-1, True)
698 yield (style_type_bool, repr(item))
695 yield (style_type_bool, repr(item))
699 elif isinstance(item, str):
696 elif isinstance(item, str):
700 yield (-1, True)
697 yield (-1, True)
701 if mode == "cell":
698 if mode == "cell":
702 yield (style_default, repr(item.expandtabs(tab))[1:-1])
699 yield (style_default, repr(item.expandtabs(tab))[1:-1])
703 else:
700 else:
704 yield (style_default, repr(item))
701 yield (style_default, repr(item))
705 elif isinstance(item, unicode):
702 elif isinstance(item, unicode):
706 yield (-1, True)
703 yield (-1, True)
707 if mode == "cell":
704 if mode == "cell":
708 yield (style_default, repr(item.expandtabs(tab))[2:-1])
705 yield (style_default, repr(item.expandtabs(tab))[2:-1])
709 else:
706 else:
710 yield (style_default, repr(item))
707 yield (style_default, repr(item))
711 elif isinstance(item, (int, long, float)):
708 elif isinstance(item, (int, long, float)):
712 yield (1, True)
709 yield (1, True)
713 yield (style_type_number, repr(item))
710 yield (style_type_number, repr(item))
714 elif isinstance(item, complex):
711 elif isinstance(item, complex):
715 yield (-1, True)
712 yield (-1, True)
716 yield (style_type_number, repr(item))
713 yield (style_type_number, repr(item))
717 elif isinstance(item, datetime.datetime):
714 elif isinstance(item, datetime.datetime):
718 yield (-1, True)
715 yield (-1, True)
719 if mode == "cell":
716 if mode == "cell":
720 # Don't use strftime() here, as this requires year >= 1900
717 # Don't use strftime() here, as this requires year >= 1900
721 yield (style_type_datetime,
718 yield (style_type_datetime,
722 "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
719 "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
723 (item.year, item.month, item.day,
720 (item.year, item.month, item.day,
724 item.hour, item.minute, item.second,
721 item.hour, item.minute, item.second,
725 item.microsecond),
722 item.microsecond),
726 )
723 )
727 else:
724 else:
728 yield (style_type_datetime, repr(item))
725 yield (style_type_datetime, repr(item))
729 elif isinstance(item, datetime.date):
726 elif isinstance(item, datetime.date):
730 yield (-1, True)
727 yield (-1, True)
731 if mode == "cell":
728 if mode == "cell":
732 yield (style_type_datetime,
729 yield (style_type_datetime,
733 "%04d-%02d-%02d" % (item.year, item.month, item.day))
730 "%04d-%02d-%02d" % (item.year, item.month, item.day))
734 else:
731 else:
735 yield (style_type_datetime, repr(item))
732 yield (style_type_datetime, repr(item))
736 elif isinstance(item, datetime.time):
733 elif isinstance(item, datetime.time):
737 yield (-1, True)
734 yield (-1, True)
738 if mode == "cell":
735 if mode == "cell":
739 yield (style_type_datetime,
736 yield (style_type_datetime,
740 "%02d:%02d:%02d.%06d" % \
737 "%02d:%02d:%02d.%06d" % \
741 (item.hour, item.minute, item.second, item.microsecond))
738 (item.hour, item.minute, item.second, item.microsecond))
742 else:
739 else:
743 yield (style_type_datetime, repr(item))
740 yield (style_type_datetime, repr(item))
744 elif isinstance(item, datetime.timedelta):
741 elif isinstance(item, datetime.timedelta):
745 yield (-1, True)
742 yield (-1, True)
746 yield (style_type_datetime, repr(item))
743 yield (style_type_datetime, repr(item))
747 elif isinstance(item, Exception):
744 elif isinstance(item, Exception):
748 yield (-1, True)
745 yield (-1, True)
749 if item.__class__.__module__ == "exceptions":
746 if item.__class__.__module__ == "exceptions":
750 classname = item.__class__.__name__
747 classname = item.__class__.__name__
751 else:
748 else:
752 classname = "%s.%s" % \
749 classname = "%s.%s" % \
753 (item.__class__.__module__, item.__class__.__name__)
750 (item.__class__.__module__, item.__class__.__name__)
754 if mode == "header" or mode == "footer":
751 if mode == "header" or mode == "footer":
755 yield (style_error, "%s: %s" % (classname, item))
752 yield (style_error, "%s: %s" % (classname, item))
756 else:
753 else:
757 yield (style_error, classname)
754 yield (style_error, classname)
758 elif isinstance(item, (list, tuple)):
755 elif isinstance(item, (list, tuple)):
759 yield (-1, False)
756 yield (-1, False)
760 if mode == "header" or mode == "footer":
757 if mode == "header" or mode == "footer":
761 if item.__class__.__module__ == "__builtin__":
758 if item.__class__.__module__ == "__builtin__":
762 classname = item.__class__.__name__
759 classname = item.__class__.__name__
763 else:
760 else:
764 classname = "%s.%s" % \
761 classname = "%s.%s" % \
765 (item.__class__.__module__,item.__class__.__name__)
762 (item.__class__.__module__,item.__class__.__name__)
766 yield (style_default,
763 yield (style_default,
767 "<%s object with %d items at 0x%x>" % \
764 "<%s object with %d items at 0x%x>" % \
768 (classname, len(item), id(item)))
765 (classname, len(item), id(item)))
769 else:
766 else:
770 if isinstance(item, list):
767 if isinstance(item, list):
771 yield (style_default, "[")
768 yield (style_default, "[")
772 end = "]"
769 end = "]"
773 else:
770 else:
774 yield (style_default, "(")
771 yield (style_default, "(")
775 end = ")"
772 end = ")"
776 for (i, subitem) in enumerate(item):
773 for (i, subitem) in enumerate(item):
777 if i:
774 if i:
778 yield (style_default, ", ")
775 yield (style_default, ", ")
779 for part in xrepr(subitem, "default"):
776 for part in xrepr(subitem, "default"):
780 yield part
777 yield part
781 yield (style_default, end)
778 yield (style_default, end)
782 elif isinstance(item, (dict, types.DictProxyType)):
779 elif isinstance(item, (dict, types.DictProxyType)):
783 yield (-1, False)
780 yield (-1, False)
784 if mode == "header" or mode == "footer":
781 if mode == "header" or mode == "footer":
785 if item.__class__.__module__ == "__builtin__":
782 if item.__class__.__module__ == "__builtin__":
786 classname = item.__class__.__name__
783 classname = item.__class__.__name__
787 else:
784 else:
788 classname = "%s.%s" % \
785 classname = "%s.%s" % \
789 (item.__class__.__module__,item.__class__.__name__)
786 (item.__class__.__module__,item.__class__.__name__)
790 yield (style_default,
787 yield (style_default,
791 "<%s object with %d items at 0x%x>" % \
788 "<%s object with %d items at 0x%x>" % \
792 (classname, len(item), id(item)))
789 (classname, len(item), id(item)))
793 else:
790 else:
794 if isinstance(item, dict):
791 if isinstance(item, dict):
795 yield (style_default, "{")
792 yield (style_default, "{")
796 end = "}"
793 end = "}"
797 else:
794 else:
798 yield (style_default, "dictproxy((")
795 yield (style_default, "dictproxy((")
799 end = "})"
796 end = "})"
800 for (i, (key, value)) in enumerate(item.iteritems()):
797 for (i, (key, value)) in enumerate(item.iteritems()):
801 if i:
798 if i:
802 yield (style_default, ", ")
799 yield (style_default, ", ")
803 for part in xrepr(key, "default"):
800 for part in xrepr(key, "default"):
804 yield part
801 yield part
805 yield (style_default, ": ")
802 yield (style_default, ": ")
806 for part in xrepr(value, "default"):
803 for part in xrepr(value, "default"):
807 yield part
804 yield part
808 yield (style_default, end)
805 yield (style_default, end)
809 else:
806 else:
810 yield (-1, True)
807 yield (-1, True)
811 yield (style_default, repr(item))
808 yield (style_default, repr(item))
812
809
813
810
814 def xattrs(item, mode):
811 def xattrs(item, mode):
815 try:
812 try:
816 func = item.__xattrs__
813 func = item.__xattrs__
817 except AttributeError:
814 except AttributeError:
818 if mode == "detail":
815 if mode == "detail":
819 return dir(item)
816 return dir(item)
820 else:
817 else:
821 return (None,)
818 return (None,)
822 else:
819 else:
823 try:
820 try:
824 return func(mode)
821 return func(mode)
825 except (KeyboardInterrupt, SystemExit):
822 except (KeyboardInterrupt, SystemExit):
826 raise
823 raise
827 except Exception:
824 except Exception:
828 return (None,)
825 return (None,)
829
826
830
827
831 def xiter(item, mode):
828 def xiter(item, mode):
832 if mode == "detail":
829 if mode == "detail":
833 def items():
830 def items():
834 for name in xattrs(item, mode):
831 for name in xattrs(item, mode):
835 yield XAttr(item, name)
832 yield XAttr(item, name)
836 return items()
833 return items()
837 try:
834 try:
838 func = item.__xiter__
835 func = item.__xiter__
839 except AttributeError:
836 except AttributeError:
840 if isinstance(item, (dict, types.DictProxyType)):
837 if isinstance(item, (dict, types.DictProxyType)):
841 def items(item):
838 def items(item):
842 fields = ("key", "value")
839 fields = ("key", "value")
843 for (key, value) in item.iteritems():
840 for (key, value) in item.iteritems():
844 yield Fields(fields, key=key, value=value)
841 yield Fields(fields, key=key, value=value)
845 return items(item)
842 return items(item)
846 elif isinstance(item, new.module):
843 elif isinstance(item, new.module):
847 def items(item):
844 def items(item):
848 fields = ("key", "value")
845 fields = ("key", "value")
849 for key in sorted(item.__dict__):
846 for key in sorted(item.__dict__):
850 yield Fields(fields, key=key, value=getattr(item, key))
847 yield Fields(fields, key=key, value=getattr(item, key))
851 return items(item)
848 return items(item)
852 elif isinstance(item, basestring):
849 elif isinstance(item, basestring):
853 if not len(item):
850 if not len(item):
854 raise ValueError("can't enter empty string")
851 raise ValueError("can't enter empty string")
855 lines = item.splitlines()
852 lines = item.splitlines()
856 if len(lines) <= 1:
853 if len(lines) <= 1:
857 raise ValueError("can't enter one line string")
854 raise ValueError("can't enter one line string")
858 return iter(lines)
855 return iter(lines)
859 return iter(item)
856 return iter(item)
860 else:
857 else:
861 return iter(func(mode)) # iter() just to be safe
858 return iter(func(mode)) # iter() just to be safe
862
859
863
860
864 class ichain(Pipe):
861 class ichain(Pipe):
865 """
862 """
866 Chains multiple ``Table``s into one.
863 Chains multiple ``Table``s into one.
867 """
864 """
868
865
869 def __init__(self, *iters):
866 def __init__(self, *iters):
870 self.iters = iters
867 self.iters = iters
871
868
872 def __xiter__(self, mode):
869 def __xiter__(self, mode):
873 return itertools.chain(*self.iters)
870 return itertools.chain(*self.iters)
874
871
875 def __xrepr__(self, mode):
872 def __xrepr__(self, mode):
876 yield (-1, True)
873 yield (-1, True)
877 if mode == "header" or mode == "footer":
874 if mode == "header" or mode == "footer":
878 for (i, item) in enumerate(self.iters):
875 for (i, item) in enumerate(self.iters):
879 if i:
876 if i:
880 yield (style_default, "+")
877 yield (style_default, "+")
881 if isinstance(item, Pipe):
878 if isinstance(item, Pipe):
882 yield (style_default, "(")
879 yield (style_default, "(")
883 for part in xrepr(item, mode):
880 for part in xrepr(item, mode):
884 yield part
881 yield part
885 if isinstance(item, Pipe):
882 if isinstance(item, Pipe):
886 yield (style_default, ")")
883 yield (style_default, ")")
887 else:
884 else:
888 yield (style_default, repr(self))
885 yield (style_default, repr(self))
889
886
890 def __repr__(self):
887 def __repr__(self):
891 args = ", ".join([repr(it) for it in self.iters])
888 args = ", ".join([repr(it) for it in self.iters])
892 return "%s.%s(%s)" % \
889 return "%s.%s(%s)" % \
893 (self.__class__.__module__, self.__class__.__name__, args)
890 (self.__class__.__module__, self.__class__.__name__, args)
894
891
895
892
896 class ifile(path.path):
893 class ifile(path.path):
897 """
894 """
898 file (or directory) object.
895 file (or directory) object.
899 """
896 """
900
897
901 def __add_(self, other):
898 def __add_(self, other):
902 return ifile(path._base(self) + other)
899 return ifile(path._base(self) + other)
903
900
904 def __radd_(self, other):
901 def __radd_(self, other):
905 return ifile(other + path._base(self))
902 return ifile(other + path._base(self))
906
903
907 def __div_(self, other):
904 def __div_(self, other):
908 return ifile(path.__div__(self, other))
905 return ifile(path.__div__(self, other))
909
906
910 def getcwd():
907 def getcwd():
911 """ Return the current working directory as a path object. """
908 """ Return the current working directory as a path object. """
912 return ifile(path.path.getcwd())
909 return ifile(path.path.getcwd())
913 getcwd = staticmethod(getcwd)
910 getcwd = staticmethod(getcwd)
914
911
915 def abspath(self):
912 def abspath(self):
916 return ifile(path.path.abspath(self))
913 return ifile(path.path.abspath(self))
917
914
918 def normcase(self):
915 def normcase(self):
919 return ifile(path.path.normcase(self))
916 return ifile(path.path.normcase(self))
920
917
921 def normpath(self):
918 def normpath(self):
922 return ifile(path.path.normpath(self))
919 return ifile(path.path.normpath(self))
923
920
924 def realpath(self):
921 def realpath(self):
925 return ifile(path.path.realpath(self))
922 return ifile(path.path.realpath(self))
926
923
927 def expanduser(self):
924 def expanduser(self):
928 return ifile(path.path.expanduser(self))
925 return ifile(path.path.expanduser(self))
929
926
930 def expandvars(self):
927 def expandvars(self):
931 return ifile(path.path.expandvars(self))
928 return ifile(path.path.expandvars(self))
932
929
933 def dirname(self):
930 def dirname(self):
934 return ifile(path.path.dirname(self))
931 return ifile(path.path.dirname(self))
935
932
936 parent = property(dirname, None, None, path.path.parent.__doc__)
933 parent = property(dirname, None, None, path.path.parent.__doc__)
937
934
938 def splitpath(self):
935 def splitpath(self):
939 (parent, child) = path.path.splitpath(self)
936 (parent, child) = path.path.splitpath(self)
940 return (ifile(parent), child)
937 return (ifile(parent), child)
941
938
942 def splitdrive(self):
939 def splitdrive(self):
943 (drive, rel) = path.path.splitdrive(self)
940 (drive, rel) = path.path.splitdrive(self)
944 return (ifile(drive), rel)
941 return (ifile(drive), rel)
945
942
946 def splitext(self):
943 def splitext(self):
947 (filename, ext) = path.path.splitext(self)
944 (filename, ext) = path.path.splitext(self)
948 return (ifile(filename), ext)
945 return (ifile(filename), ext)
949
946
950 if hasattr(path.path, "splitunc"):
947 if hasattr(path.path, "splitunc"):
951 def splitunc(self):
948 def splitunc(self):
952 (unc, rest) = path.path.splitunc(self)
949 (unc, rest) = path.path.splitunc(self)
953 return (ifile(unc), rest)
950 return (ifile(unc), rest)
954
951
955 def _get_uncshare(self):
952 def _get_uncshare(self):
956 unc, r = os.path.splitunc(self)
953 unc, r = os.path.splitunc(self)
957 return ifile(unc)
954 return ifile(unc)
958
955
959 uncshare = property(
956 uncshare = property(
960 _get_uncshare, None, None,
957 _get_uncshare, None, None,
961 """ The UNC mount point for this path.
958 """ The UNC mount point for this path.
962 This is empty for paths on local drives. """)
959 This is empty for paths on local drives. """)
963
960
964 def joinpath(self, *args):
961 def joinpath(self, *args):
965 return ifile(path.path.joinpath(self, *args))
962 return ifile(path.path.joinpath(self, *args))
966
963
967 def splitall(self):
964 def splitall(self):
968 return map(ifile, path.path.splitall(self))
965 return map(ifile, path.path.splitall(self))
969
966
970 def relpath(self):
967 def relpath(self):
971 return ifile(path.path.relpath(self))
968 return ifile(path.path.relpath(self))
972
969
973 def relpathto(self, dest):
970 def relpathto(self, dest):
974 return ifile(path.path.relpathto(self, dest))
971 return ifile(path.path.relpathto(self, dest))
975
972
976 def listdir(self, pattern=None):
973 def listdir(self, pattern=None):
977 return [ifile(child) for child in path.path.listdir(self, pattern)]
974 return [ifile(child) for child in path.path.listdir(self, pattern)]
978
975
979 def dirs(self, pattern=None):
976 def dirs(self, pattern=None):
980 return [ifile(child) for child in path.path.dirs(self, pattern)]
977 return [ifile(child) for child in path.path.dirs(self, pattern)]
981
978
982 def files(self, pattern=None):
979 def files(self, pattern=None):
983 return [ifile(child) for child in path.path.files(self, pattern)]
980 return [ifile(child) for child in path.path.files(self, pattern)]
984
981
985 def walk(self, pattern=None):
982 def walk(self, pattern=None):
986 for child in path.path.walk(self, pattern):
983 for child in path.path.walk(self, pattern):
987 yield ifile(child)
984 yield ifile(child)
988
985
989 def walkdirs(self, pattern=None):
986 def walkdirs(self, pattern=None):
990 for child in path.path.walkdirs(self, pattern):
987 for child in path.path.walkdirs(self, pattern):
991 yield ifile(child)
988 yield ifile(child)
992
989
993 def walkfiles(self, pattern=None):
990 def walkfiles(self, pattern=None):
994 for child in path.path.walkfiles(self, pattern):
991 for child in path.path.walkfiles(self, pattern):
995 yield ifile(child)
992 yield ifile(child)
996
993
997 def glob(self, pattern):
994 def glob(self, pattern):
998 return map(ifile, path.path.glob(self, pattern))
995 return map(ifile, path.path.glob(self, pattern))
999
996
1000 if hasattr(os, 'readlink'):
997 if hasattr(os, 'readlink'):
1001 def readlink(self):
998 def readlink(self):
1002 return ifile(path.path.readlink(self))
999 return ifile(path.path.readlink(self))
1003
1000
1004 def readlinkabs(self):
1001 def readlinkabs(self):
1005 return ifile(path.path.readlinkabs(self))
1002 return ifile(path.path.readlinkabs(self))
1006
1003
1007 def getmode(self):
1004 def getmode(self):
1008 return self.stat().st_mode
1005 return self.stat().st_mode
1009 mode = property(getmode, None, None, "Access mode")
1006 mode = property(getmode, None, None, "Access mode")
1010
1007
1011 def gettype(self):
1008 def gettype(self):
1012 data = [
1009 data = [
1013 (stat.S_ISREG, "file"),
1010 (stat.S_ISREG, "file"),
1014 (stat.S_ISDIR, "dir"),
1011 (stat.S_ISDIR, "dir"),
1015 (stat.S_ISCHR, "chardev"),
1012 (stat.S_ISCHR, "chardev"),
1016 (stat.S_ISBLK, "blockdev"),
1013 (stat.S_ISBLK, "blockdev"),
1017 (stat.S_ISFIFO, "fifo"),
1014 (stat.S_ISFIFO, "fifo"),
1018 (stat.S_ISLNK, "symlink"),
1015 (stat.S_ISLNK, "symlink"),
1019 (stat.S_ISSOCK,"socket"),
1016 (stat.S_ISSOCK,"socket"),
1020 ]
1017 ]
1021 lstat = self.lstat()
1018 lstat = self.lstat()
1022 if lstat is not None:
1019 if lstat is not None:
1023 types = set([text for (func, text) in data if func(lstat.st_mode)])
1020 types = set([text for (func, text) in data if func(lstat.st_mode)])
1024 else:
1021 else:
1025 types = set()
1022 types = set()
1026 m = self.mode
1023 m = self.mode
1027 types.update([text for (func, text) in data if func(m)])
1024 types.update([text for (func, text) in data if func(m)])
1028 return ", ".join(types)
1025 return ", ".join(types)
1029 type = property(gettype, None, None, "file type (file, directory, link, etc.)")
1026 type = property(gettype, None, None, "file type (file, directory, link, etc.)")
1030
1027
1031 def getmodestr(self):
1028 def getmodestr(self):
1032 m = self.mode
1029 m = self.mode
1033 data = [
1030 data = [
1034 (stat.S_IRUSR, "-r"),
1031 (stat.S_IRUSR, "-r"),
1035 (stat.S_IWUSR, "-w"),
1032 (stat.S_IWUSR, "-w"),
1036 (stat.S_IXUSR, "-x"),
1033 (stat.S_IXUSR, "-x"),
1037 (stat.S_IRGRP, "-r"),
1034 (stat.S_IRGRP, "-r"),
1038 (stat.S_IWGRP, "-w"),
1035 (stat.S_IWGRP, "-w"),
1039 (stat.S_IXGRP, "-x"),
1036 (stat.S_IXGRP, "-x"),
1040 (stat.S_IROTH, "-r"),
1037 (stat.S_IROTH, "-r"),
1041 (stat.S_IWOTH, "-w"),
1038 (stat.S_IWOTH, "-w"),
1042 (stat.S_IXOTH, "-x"),
1039 (stat.S_IXOTH, "-x"),
1043 ]
1040 ]
1044 return "".join([text[bool(m&bit)] for (bit, text) in data])
1041 return "".join([text[bool(m&bit)] for (bit, text) in data])
1045
1042
1046 modestr = property(getmodestr, None, None, "Access mode as string")
1043 modestr = property(getmodestr, None, None, "Access mode as string")
1047
1044
1048 def getblocks(self):
1045 def getblocks(self):
1049 return self.stat().st_blocks
1046 return self.stat().st_blocks
1050 blocks = property(getblocks, None, None, "File size in blocks")
1047 blocks = property(getblocks, None, None, "File size in blocks")
1051
1048
1052 def getblksize(self):
1049 def getblksize(self):
1053 return self.stat().st_blksize
1050 return self.stat().st_blksize
1054 blksize = property(getblksize, None, None, "Filesystem block size")
1051 blksize = property(getblksize, None, None, "Filesystem block size")
1055
1052
1056 def getdev(self):
1053 def getdev(self):
1057 return self.stat().st_dev
1054 return self.stat().st_dev
1058 dev = property(getdev)
1055 dev = property(getdev)
1059
1056
1060 def getnlink(self):
1057 def getnlink(self):
1061 return self.stat().st_nlink
1058 return self.stat().st_nlink
1062 nlink = property(getnlink, None, None, "Number of links")
1059 nlink = property(getnlink, None, None, "Number of links")
1063
1060
1064 def getuid(self):
1061 def getuid(self):
1065 return self.stat().st_uid
1062 return self.stat().st_uid
1066 uid = property(getuid, None, None, "User id of file owner")
1063 uid = property(getuid, None, None, "User id of file owner")
1067
1064
1068 def getgid(self):
1065 def getgid(self):
1069 return self.stat().st_gid
1066 return self.stat().st_gid
1070 gid = property(getgid, None, None, "Group id of file owner")
1067 gid = property(getgid, None, None, "Group id of file owner")
1071
1068
1072 def getowner(self):
1069 def getowner(self):
1073 stat = self.stat()
1070 stat = self.stat()
1074 try:
1071 try:
1075 return pwd.getpwuid(stat.st_uid).pw_name
1072 return pwd.getpwuid(stat.st_uid).pw_name
1076 except KeyError:
1073 except KeyError:
1077 return stat.st_uid
1074 return stat.st_uid
1078 owner = property(getowner, None, None, "Owner name (or id)")
1075 owner = property(getowner, None, None, "Owner name (or id)")
1079
1076
1080 def getgroup(self):
1077 def getgroup(self):
1081 stat = self.stat()
1078 stat = self.stat()
1082 try:
1079 try:
1083 return grp.getgrgid(stat.st_gid).gr_name
1080 return grp.getgrgid(stat.st_gid).gr_name
1084 except KeyError:
1081 except KeyError:
1085 return stat.st_gid
1082 return stat.st_gid
1086 group = property(getgroup, None, None, "Group name (or id)")
1083 group = property(getgroup, None, None, "Group name (or id)")
1087
1084
1088 def getadate(self):
1085 def getadate(self):
1089 return datetime.datetime.utcfromtimestamp(self.atime)
1086 return datetime.datetime.utcfromtimestamp(self.atime)
1090 adate = property(getadate, None, None, "Access date")
1087 adate = property(getadate, None, None, "Access date")
1091
1088
1092 def getcdate(self):
1089 def getcdate(self):
1093 return datetime.datetime.utcfromtimestamp(self.ctime)
1090 return datetime.datetime.utcfromtimestamp(self.ctime)
1094 cdate = property(getcdate, None, None, "Creation date")
1091 cdate = property(getcdate, None, None, "Creation date")
1095
1092
1096 def getmdate(self):
1093 def getmdate(self):
1097 return datetime.datetime.utcfromtimestamp(self.mtime)
1094 return datetime.datetime.utcfromtimestamp(self.mtime)
1098 mdate = property(getmdate, None, None, "Modification date")
1095 mdate = property(getmdate, None, None, "Modification date")
1099
1096
1100 def getmimetype(self):
1097 def getmimetype(self):
1101 return mimetypes.guess_type(self.basename())[0]
1098 return mimetypes.guess_type(self.basename())[0]
1102 mimetype = property(getmimetype, None, None, "MIME type")
1099 mimetype = property(getmimetype, None, None, "MIME type")
1103
1100
1104 def getencoding(self):
1101 def getencoding(self):
1105 return mimetypes.guess_type(self.basename())[1]
1102 return mimetypes.guess_type(self.basename())[1]
1106 encoding = property(getencoding, None, None, "Compression")
1103 encoding = property(getencoding, None, None, "Compression")
1107
1104
1108 def __repr__(self):
1105 def __repr__(self):
1109 return "ifile(%s)" % path._base.__repr__(self)
1106 return "ifile(%s)" % path._base.__repr__(self)
1110
1107
1111 defaultattrs = (None, "type", "size", "modestr", "owner", "group", "mdate")
1108 defaultattrs = (None, "type", "size", "modestr", "owner", "group", "mdate")
1112
1109
1113 def __xattrs__(self, mode):
1110 def __xattrs__(self, mode):
1114 if mode == "detail":
1111 if mode == "detail":
1115 return (
1112 return (
1116 "name", "basename()", "abspath()", "realpath()",
1113 "name", "basename()", "abspath()", "realpath()",
1117 "type", "mode", "modestr", "stat()", "lstat()",
1114 "type", "mode", "modestr", "stat()", "lstat()",
1118 "uid", "gid", "owner", "group", "dev", "nlink",
1115 "uid", "gid", "owner", "group", "dev", "nlink",
1119 "ctime", "mtime", "atime", "cdate", "mdate", "adate",
1116 "ctime", "mtime", "atime", "cdate", "mdate", "adate",
1120 "size", "blocks", "blksize", "isdir()", "islink()",
1117 "size", "blocks", "blksize", "isdir()", "islink()",
1121 "mimetype", "encoding"
1118 "mimetype", "encoding"
1122 )
1119 )
1123 return self.defaultattrs
1120 return self.defaultattrs
1124
1121
1125 def __xrepr__(self, mode):
1122 def __xrepr__(self, mode):
1126 yield (-1, True)
1123 yield (-1, True)
1127 try:
1124 try:
1128 if self.isdir():
1125 if self.isdir():
1129 name = "idir"
1126 name = "idir"
1130 style = style_dir
1127 style = style_dir
1131 else:
1128 else:
1132 name = "ifile"
1129 name = "ifile"
1133 style = style_file
1130 style = style_file
1134 except IOError:
1131 except IOError:
1135 name = "ifile"
1132 name = "ifile"
1136 style = style_default
1133 style = style_default
1137 if mode == "cell" or mode in "header" or mode == "footer":
1134 if mode == "cell" or mode in "header" or mode == "footer":
1138 abspath = repr(path._base(self.normpath()))
1135 abspath = repr(path._base(self.normpath()))
1139 if abspath.startswith("u"):
1136 if abspath.startswith("u"):
1140 abspath = abspath[2:-1]
1137 abspath = abspath[2:-1]
1141 else:
1138 else:
1142 abspath = abspath[1:-1]
1139 abspath = abspath[1:-1]
1143 if mode == "cell":
1140 if mode == "cell":
1144 yield (style, abspath)
1141 yield (style, abspath)
1145 else:
1142 else:
1146 yield (style, "%s(%s)" % (name, abspath))
1143 yield (style, "%s(%s)" % (name, abspath))
1147 else:
1144 else:
1148 yield (style, repr(self))
1145 yield (style, repr(self))
1149
1146
1150 def __xiter__(self, mode):
1147 def __xiter__(self, mode):
1151 if self.isdir():
1148 if self.isdir():
1152 yield iparentdir(self / os.pardir)
1149 yield iparentdir(self / os.pardir)
1153 for child in sorted(self.listdir()):
1150 for child in sorted(self.listdir()):
1154 yield child
1151 yield child
1155 else:
1152 else:
1156 f = self.open("rb")
1153 f = self.open("rb")
1157 for line in f:
1154 for line in f:
1158 yield line
1155 yield line
1159 f.close()
1156 f.close()
1160
1157
1161
1158
1162 class iparentdir(ifile):
1159 class iparentdir(ifile):
1163 def __xrepr__(self, mode):
1160 def __xrepr__(self, mode):
1164 yield (-1, True)
1161 yield (-1, True)
1165 if mode == "cell":
1162 if mode == "cell":
1166 yield (style_dir, os.pardir)
1163 yield (style_dir, os.pardir)
1167 else:
1164 else:
1168 for part in ifile.__xrepr__(self, mode):
1165 for part in ifile.__xrepr__(self, mode):
1169 yield part
1166 yield part
1170
1167
1171
1168
1172 class ils(Table):
1169 class ils(Table):
1173 """
1170 """
1174 List the current (or a specific) directory.
1171 List the current (or a specific) directory.
1175
1172
1176 Examples:
1173 Examples:
1177
1174
1178 >>> ils
1175 >>> ils
1179 >>> ils("/usr/local/lib/python2.4")
1176 >>> ils("/usr/local/lib/python2.4")
1180 >>> ils("~")
1177 >>> ils("~")
1181 """
1178 """
1182 def __init__(self, base=os.curdir):
1179 def __init__(self, base=os.curdir):
1183 self.base = os.path.expanduser(base)
1180 self.base = os.path.expanduser(base)
1184
1181
1185 def __xiter__(self, mode):
1182 def __xiter__(self, mode):
1186 return xiter(ifile(self.base), mode)
1183 return xiter(ifile(self.base), mode)
1187
1184
1188 def __xrepr__(self, mode):
1185 def __xrepr__(self, mode):
1189 return ifile(self.base).__xrepr__(mode)
1186 return ifile(self.base).__xrepr__(mode)
1190
1187
1191 def __repr__(self):
1188 def __repr__(self):
1192 return "%s.%s(%r)" % \
1189 return "%s.%s(%r)" % \
1193 (self.__class__.__module__, self.__class__.__name__, self.base)
1190 (self.__class__.__module__, self.__class__.__name__, self.base)
1194
1191
1195
1192
1196 class iglob(Table):
1193 class iglob(Table):
1197 """
1194 """
1198 List all files and directories matching a specified pattern.
1195 List all files and directories matching a specified pattern.
1199 (See ``glob.glob()`` for more info.).
1196 (See ``glob.glob()`` for more info.).
1200
1197
1201 Examples:
1198 Examples:
1202
1199
1203 >>> iglob("*.py")
1200 >>> iglob("*.py")
1204 """
1201 """
1205 def __init__(self, glob):
1202 def __init__(self, glob):
1206 self.glob = glob
1203 self.glob = glob
1207
1204
1208 def __xiter__(self, mode):
1205 def __xiter__(self, mode):
1209 for name in glob.glob(self.glob):
1206 for name in glob.glob(self.glob):
1210 yield ifile(name)
1207 yield ifile(name)
1211
1208
1212 def __xrepr__(self, mode):
1209 def __xrepr__(self, mode):
1213 yield (-1, True)
1210 yield (-1, True)
1214 if mode == "header" or mode == "footer" or mode == "cell":
1211 if mode == "header" or mode == "footer" or mode == "cell":
1215 yield (style_default, "%s(%r)" % (self.__class__.__name__, self.glob))
1212 yield (style_default, "%s(%r)" % (self.__class__.__name__, self.glob))
1216 else:
1213 else:
1217 yield (style_default, repr(self))
1214 yield (style_default, repr(self))
1218
1215
1219 def __repr__(self):
1216 def __repr__(self):
1220 return "%s.%s(%r)" % \
1217 return "%s.%s(%r)" % \
1221 (self.__class__.__module__, self.__class__.__name__, self.glob)
1218 (self.__class__.__module__, self.__class__.__name__, self.glob)
1222
1219
1223
1220
1224 class iwalk(Table):
1221 class iwalk(Table):
1225 """
1222 """
1226 List all files and directories in a directory and it's subdirectory.
1223 List all files and directories in a directory and it's subdirectory.
1227
1224
1228 >>> iwalk
1225 >>> iwalk
1229 >>> iwalk("/usr/local/lib/python2.4")
1226 >>> iwalk("/usr/local/lib/python2.4")
1230 >>> iwalk("~")
1227 >>> iwalk("~")
1231 """
1228 """
1232 def __init__(self, base=os.curdir, dirs=True, files=True):
1229 def __init__(self, base=os.curdir, dirs=True, files=True):
1233 self.base = os.path.expanduser(base)
1230 self.base = os.path.expanduser(base)
1234 self.dirs = dirs
1231 self.dirs = dirs
1235 self.files = files
1232 self.files = files
1236
1233
1237 def __xiter__(self, mode):
1234 def __xiter__(self, mode):
1238 for (dirpath, dirnames, filenames) in os.walk(self.base):
1235 for (dirpath, dirnames, filenames) in os.walk(self.base):
1239 if self.dirs:
1236 if self.dirs:
1240 for name in sorted(dirnames):
1237 for name in sorted(dirnames):
1241 yield ifile(os.path.join(dirpath, name))
1238 yield ifile(os.path.join(dirpath, name))
1242 if self.files:
1239 if self.files:
1243 for name in sorted(filenames):
1240 for name in sorted(filenames):
1244 yield ifile(os.path.join(dirpath, name))
1241 yield ifile(os.path.join(dirpath, name))
1245
1242
1246 def __xrepr__(self, mode):
1243 def __xrepr__(self, mode):
1247 yield (-1, True)
1244 yield (-1, True)
1248 if mode == "header" or mode == "footer" or mode == "cell":
1245 if mode == "header" or mode == "footer" or mode == "cell":
1249 yield (style_default, "%s(%r)" % (self.__class__.__name__, self.base))
1246 yield (style_default, "%s(%r)" % (self.__class__.__name__, self.base))
1250 else:
1247 else:
1251 yield (style_default, repr(self))
1248 yield (style_default, repr(self))
1252
1249
1253 def __repr__(self):
1250 def __repr__(self):
1254 return "%s.%s(%r)" % \
1251 return "%s.%s(%r)" % \
1255 (self.__class__.__module__, self.__class__.__name__, self.base)
1252 (self.__class__.__module__, self.__class__.__name__, self.base)
1256
1253
1257
1254
1258 class ipwdentry(object):
1255 class ipwdentry(object):
1259 """
1256 """
1260 ``ipwdentry`` objects encapsulate entries in the Unix user account and
1257 ``ipwdentry`` objects encapsulate entries in the Unix user account and
1261 password database.
1258 password database.
1262 """
1259 """
1263 def __init__(self, id):
1260 def __init__(self, id):
1264 self._id = id
1261 self._id = id
1265 self._entry = None
1262 self._entry = None
1266
1263
1267 def _getentry(self):
1264 def _getentry(self):
1268 if self._entry is None:
1265 if self._entry is None:
1269 if isinstance(self._id, basestring):
1266 if isinstance(self._id, basestring):
1270 self._entry = pwd.getpwnam(self._id)
1267 self._entry = pwd.getpwnam(self._id)
1271 else:
1268 else:
1272 self._entry = pwd.getpwuid(self._id)
1269 self._entry = pwd.getpwuid(self._id)
1273 return self._entry
1270 return self._entry
1274
1271
1275 def getname(self):
1272 def getname(self):
1276 if isinstance(self._id, basestring):
1273 if isinstance(self._id, basestring):
1277 return self._id
1274 return self._id
1278 else:
1275 else:
1279 return self._getentry().pw_name
1276 return self._getentry().pw_name
1280 name = property(getname, None, None, "User name")
1277 name = property(getname, None, None, "User name")
1281
1278
1282 def getpasswd(self):
1279 def getpasswd(self):
1283 return self._getentry().pw_passwd
1280 return self._getentry().pw_passwd
1284 passwd = property(getpasswd, None, None, "Password")
1281 passwd = property(getpasswd, None, None, "Password")
1285
1282
1286 def getuid(self):
1283 def getuid(self):
1287 if isinstance(self._id, basestring):
1284 if isinstance(self._id, basestring):
1288 return self._getentry().pw_uid
1285 return self._getentry().pw_uid
1289 else:
1286 else:
1290 return self._id
1287 return self._id
1291 uid = property(getuid, None, None, "User id")
1288 uid = property(getuid, None, None, "User id")
1292
1289
1293 def getgid(self):
1290 def getgid(self):
1294 return self._getentry().pw_gid
1291 return self._getentry().pw_gid
1295 gid = property(getgid, None, None, "Primary group id")
1292 gid = property(getgid, None, None, "Primary group id")
1296
1293
1297 def getgroup(self):
1294 def getgroup(self):
1298 return igrpentry(self.gid)
1295 return igrpentry(self.gid)
1299 group = property(getgroup, None, None, "Group")
1296 group = property(getgroup, None, None, "Group")
1300
1297
1301 def getgecos(self):
1298 def getgecos(self):
1302 return self._getentry().pw_gecos
1299 return self._getentry().pw_gecos
1303 gecos = property(getgecos, None, None, "Information (e.g. full user name)")
1300 gecos = property(getgecos, None, None, "Information (e.g. full user name)")
1304
1301
1305 def getdir(self):
1302 def getdir(self):
1306 return self._getentry().pw_dir
1303 return self._getentry().pw_dir
1307 dir = property(getdir, None, None, "$HOME directory")
1304 dir = property(getdir, None, None, "$HOME directory")
1308
1305
1309 def getshell(self):
1306 def getshell(self):
1310 return self._getentry().pw_shell
1307 return self._getentry().pw_shell
1311 shell = property(getshell, None, None, "Login shell")
1308 shell = property(getshell, None, None, "Login shell")
1312
1309
1313 def __xattrs__(self, mode):
1310 def __xattrs__(self, mode):
1314 return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell")
1311 return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell")
1315
1312
1316 def __repr__(self):
1313 def __repr__(self):
1317 return "%s.%s(%r)" % \
1314 return "%s.%s(%r)" % \
1318 (self.__class__.__module__, self.__class__.__name__, self._id)
1315 (self.__class__.__module__, self.__class__.__name__, self._id)
1319
1316
1320
1317
1321 class ipwd(Table):
1318 class ipwd(Table):
1322 """
1319 """
1323 List all entries in the Unix user account and password database.
1320 List all entries in the Unix user account and password database.
1324
1321
1325 Example:
1322 Example:
1326
1323
1327 >>> ipwd | isort("uid")
1324 >>> ipwd | isort("uid")
1328 """
1325 """
1329 def __iter__(self):
1326 def __iter__(self):
1330 for entry in pwd.getpwall():
1327 for entry in pwd.getpwall():
1331 yield ipwdentry(entry.pw_name)
1328 yield ipwdentry(entry.pw_name)
1332
1329
1333 def __xrepr__(self, mode):
1330 def __xrepr__(self, mode):
1334 yield (-1, True)
1331 yield (-1, True)
1335 if mode == "header" or mode == "footer" or mode == "cell":
1332 if mode == "header" or mode == "footer" or mode == "cell":
1336 yield (style_default, "%s()" % self.__class__.__name__)
1333 yield (style_default, "%s()" % self.__class__.__name__)
1337 else:
1334 else:
1338 yield (style_default, repr(self))
1335 yield (style_default, repr(self))
1339
1336
1340
1337
1341 class igrpentry(object):
1338 class igrpentry(object):
1342 """
1339 """
1343 ``igrpentry`` objects encapsulate entries in the Unix group database.
1340 ``igrpentry`` objects encapsulate entries in the Unix group database.
1344 """
1341 """
1345 def __init__(self, id):
1342 def __init__(self, id):
1346 self._id = id
1343 self._id = id
1347 self._entry = None
1344 self._entry = None
1348
1345
1349 def _getentry(self):
1346 def _getentry(self):
1350 if self._entry is None:
1347 if self._entry is None:
1351 if isinstance(self._id, basestring):
1348 if isinstance(self._id, basestring):
1352 self._entry = grp.getgrnam(self._id)
1349 self._entry = grp.getgrnam(self._id)
1353 else:
1350 else:
1354 self._entry = grp.getgrgid(self._id)
1351 self._entry = grp.getgrgid(self._id)
1355 return self._entry
1352 return self._entry
1356
1353
1357 def getname(self):
1354 def getname(self):
1358 if isinstance(self._id, basestring):
1355 if isinstance(self._id, basestring):
1359 return self._id
1356 return self._id
1360 else:
1357 else:
1361 return self._getentry().gr_name
1358 return self._getentry().gr_name
1362 name = property(getname, None, None, "Group name")
1359 name = property(getname, None, None, "Group name")
1363
1360
1364 def getpasswd(self):
1361 def getpasswd(self):
1365 return self._getentry().gr_passwd
1362 return self._getentry().gr_passwd
1366 passwd = property(getpasswd, None, None, "Password")
1363 passwd = property(getpasswd, None, None, "Password")
1367
1364
1368 def getgid(self):
1365 def getgid(self):
1369 if isinstance(self._id, basestring):
1366 if isinstance(self._id, basestring):
1370 return self._getentry().gr_gid
1367 return self._getentry().gr_gid
1371 else:
1368 else:
1372 return self._id
1369 return self._id
1373 gid = property(getgid, None, None, "Group id")
1370 gid = property(getgid, None, None, "Group id")
1374
1371
1375 def getmem(self):
1372 def getmem(self):
1376 return self._getentry().gr_mem
1373 return self._getentry().gr_mem
1377 mem = property(getmem, None, None, "Members")
1374 mem = property(getmem, None, None, "Members")
1378
1375
1379 def __xattrs__(self, mode):
1376 def __xattrs__(self, mode):
1380 return ("name", "passwd", "gid", "mem")
1377 return ("name", "passwd", "gid", "mem")
1381
1378
1382 def __xrepr__(self, mode):
1379 def __xrepr__(self, mode):
1383 yield (-1, True)
1380 yield (-1, True)
1384 if mode == "header" or mode == "footer" or mode == "cell":
1381 if mode == "header" or mode == "footer" or mode == "cell":
1385 yield (style_default, "group ")
1382 yield (style_default, "group ")
1386 try:
1383 try:
1387 yield (style_default, self.name)
1384 yield (style_default, self.name)
1388 except KeyError:
1385 except KeyError:
1389 if isinstance(self._id, basestring):
1386 if isinstance(self._id, basestring):
1390 yield (style_default, self.name_id)
1387 yield (style_default, self.name_id)
1391 else:
1388 else:
1392 yield (style_type_number, str(self._id))
1389 yield (style_type_number, str(self._id))
1393 else:
1390 else:
1394 yield (style_default, repr(self))
1391 yield (style_default, repr(self))
1395
1392
1396 def __xiter__(self, mode):
1393 def __xiter__(self, mode):
1397 for member in self.mem:
1394 for member in self.mem:
1398 yield ipwdentry(member)
1395 yield ipwdentry(member)
1399
1396
1400 def __repr__(self):
1397 def __repr__(self):
1401 return "%s.%s(%r)" % \
1398 return "%s.%s(%r)" % \
1402 (self.__class__.__module__, self.__class__.__name__, self._id)
1399 (self.__class__.__module__, self.__class__.__name__, self._id)
1403
1400
1404
1401
1405 class igrp(Table):
1402 class igrp(Table):
1406 """
1403 """
1407 This ``Table`` lists all entries in the Unix group database.
1404 This ``Table`` lists all entries in the Unix group database.
1408 """
1405 """
1409 def __xiter__(self, mode):
1406 def __xiter__(self, mode):
1410 for entry in grp.getgrall():
1407 for entry in grp.getgrall():
1411 yield igrpentry(entry.gr_name)
1408 yield igrpentry(entry.gr_name)
1412
1409
1413 def __xrepr__(self, mode):
1410 def __xrepr__(self, mode):
1414 yield (-1, False)
1411 yield (-1, False)
1415 if mode == "header" or mode == "footer":
1412 if mode == "header" or mode == "footer":
1416 yield (style_default, "%s()" % self.__class__.__name__)
1413 yield (style_default, "%s()" % self.__class__.__name__)
1417 else:
1414 else:
1418 yield (style_default, repr(self))
1415 yield (style_default, repr(self))
1419
1416
1420
1417
1421 class Fields(object):
1418 class Fields(object):
1422 def __init__(self, fieldnames, **fields):
1419 def __init__(self, fieldnames, **fields):
1423 self.__fieldnames = fieldnames
1420 self.__fieldnames = fieldnames
1424 for (key, value) in fields.iteritems():
1421 for (key, value) in fields.iteritems():
1425 setattr(self, key, value)
1422 setattr(self, key, value)
1426
1423
1427 def __xattrs__(self, mode):
1424 def __xattrs__(self, mode):
1428 return self.__fieldnames
1425 return self.__fieldnames
1429
1426
1430 def __xrepr__(self, mode):
1427 def __xrepr__(self, mode):
1431 yield (-1, False)
1428 yield (-1, False)
1432 if mode == "header" or mode == "cell":
1429 if mode == "header" or mode == "cell":
1433 yield (style_default, self.__class__.__name__)
1430 yield (style_default, self.__class__.__name__)
1434 yield (style_default, "(")
1431 yield (style_default, "(")
1435 for (i, f) in enumerate(self.__fieldnames):
1432 for (i, f) in enumerate(self.__fieldnames):
1436 if i:
1433 if i:
1437 yield (style_default, ", ")
1434 yield (style_default, ", ")
1438 yield (style_default, f)
1435 yield (style_default, f)
1439 yield (style_default, "=")
1436 yield (style_default, "=")
1440 for part in xrepr(getattr(self, f), "default"):
1437 for part in xrepr(getattr(self, f), "default"):
1441 yield part
1438 yield part
1442 yield (style_default, ")")
1439 yield (style_default, ")")
1443 elif mode == "footer":
1440 elif mode == "footer":
1444 yield (style_default, self.__class__.__name__)
1441 yield (style_default, self.__class__.__name__)
1445 yield (style_default, "(")
1442 yield (style_default, "(")
1446 for (i, f) in enumerate(self.__fieldnames):
1443 for (i, f) in enumerate(self.__fieldnames):
1447 if i:
1444 if i:
1448 yield (style_default, ", ")
1445 yield (style_default, ", ")
1449 yield (style_default, f)
1446 yield (style_default, f)
1450 yield (style_default, ")")
1447 yield (style_default, ")")
1451 else:
1448 else:
1452 yield (style_default, repr(self))
1449 yield (style_default, repr(self))
1453
1450
1454
1451
1455 class FieldTable(Table, list):
1452 class FieldTable(Table, list):
1456 def __init__(self, *fields):
1453 def __init__(self, *fields):
1457 Table.__init__(self)
1454 Table.__init__(self)
1458 list.__init__(self)
1455 list.__init__(self)
1459 self.fields = fields
1456 self.fields = fields
1460
1457
1461 def add(self, **fields):
1458 def add(self, **fields):
1462 self.append(Fields(self.fields, **fields))
1459 self.append(Fields(self.fields, **fields))
1463
1460
1464 def __xiter__(self, mode):
1461 def __xiter__(self, mode):
1465 return list.__iter__(self)
1462 return list.__iter__(self)
1466
1463
1467 def __xrepr__(self, mode):
1464 def __xrepr__(self, mode):
1468 yield (-1, False)
1465 yield (-1, False)
1469 if mode == "header" or mode == "footer":
1466 if mode == "header" or mode == "footer":
1470 yield (style_default, self.__class__.__name__)
1467 yield (style_default, self.__class__.__name__)
1471 yield (style_default, "(")
1468 yield (style_default, "(")
1472 for (i, f) in enumerate(self.__fieldnames):
1469 for (i, f) in enumerate(self.__fieldnames):
1473 if i:
1470 if i:
1474 yield (style_default, ", ")
1471 yield (style_default, ", ")
1475 yield (style_default, f)
1472 yield (style_default, f)
1476 yield (style_default, ")")
1473 yield (style_default, ")")
1477 else:
1474 else:
1478 yield (style_default, repr(self))
1475 yield (style_default, repr(self))
1479
1476
1480 def __repr__(self):
1477 def __repr__(self):
1481 return "<%s.%s object with fields=%r at 0x%x>" % \
1478 return "<%s.%s object with fields=%r at 0x%x>" % \
1482 (self.__class__.__module__, self.__class__.__name__,
1479 (self.__class__.__module__, self.__class__.__name__,
1483 ", ".join(map(repr, self.fields)), id(self))
1480 ", ".join(map(repr, self.fields)), id(self))
1484
1481
1485
1482
1486 class List(list):
1483 class List(list):
1487 def __xattrs__(self, mode):
1484 def __xattrs__(self, mode):
1488 return xrange(len(self))
1485 return xrange(len(self))
1489
1486
1490 def __xrepr__(self, mode):
1487 def __xrepr__(self, mode):
1491 yield (-1, False)
1488 yield (-1, False)
1492 if mode == "header" or mode == "cell" or mode == "footer" or mode == "default":
1489 if mode == "header" or mode == "cell" or mode == "footer" or mode == "default":
1493 yield (style_default, self.__class__.__name__)
1490 yield (style_default, self.__class__.__name__)
1494 yield (style_default, "(")
1491 yield (style_default, "(")
1495 for (i, item) in enumerate(self):
1492 for (i, item) in enumerate(self):
1496 if i:
1493 if i:
1497 yield (style_default, ", ")
1494 yield (style_default, ", ")
1498 for part in xrepr(item, "default"):
1495 for part in xrepr(item, "default"):
1499 yield part
1496 yield part
1500 yield (style_default, ")")
1497 yield (style_default, ")")
1501 else:
1498 else:
1502 yield (style_default, repr(self))
1499 yield (style_default, repr(self))
1503
1500
1504
1501
1505 class ienv(Table):
1502 class ienv(Table):
1506 """
1503 """
1507 List environment variables.
1504 List environment variables.
1508
1505
1509 Example:
1506 Example:
1510
1507
1511 >>> ienv
1508 >>> ienv
1512 """
1509 """
1513
1510
1514 def __xiter__(self, mode):
1511 def __xiter__(self, mode):
1515 fields = ("key", "value")
1512 fields = ("key", "value")
1516 for (key, value) in os.environ.iteritems():
1513 for (key, value) in os.environ.iteritems():
1517 yield Fields(fields, key=key, value=value)
1514 yield Fields(fields, key=key, value=value)
1518
1515
1519 def __xrepr__(self, mode):
1516 def __xrepr__(self, mode):
1520 yield (-1, True)
1517 yield (-1, True)
1521 if mode == "header" or mode == "cell":
1518 if mode == "header" or mode == "cell":
1522 yield (style_default, "%s()" % self.__class__.__name__)
1519 yield (style_default, "%s()" % self.__class__.__name__)
1523 else:
1520 else:
1524 yield (style_default, repr(self))
1521 yield (style_default, repr(self))
1525
1522
1526
1523
1527 class icsv(Pipe):
1524 class icsv(Pipe):
1528 """
1525 """
1529 This ``Pipe`` lists turn the input (with must be a pipe outputting lines
1526 This ``Pipe`` lists turn the input (with must be a pipe outputting lines
1530 or an ``ifile``) into lines of CVS columns.
1527 or an ``ifile``) into lines of CVS columns.
1531 """
1528 """
1532 def __init__(self, **csvargs):
1529 def __init__(self, **csvargs):
1533 """
1530 """
1534 Create an ``icsv`` object. ``cvsargs`` will be passed through as
1531 Create an ``icsv`` object. ``cvsargs`` will be passed through as
1535 keyword arguments to ``cvs.reader()``.
1532 keyword arguments to ``cvs.reader()``.
1536 """
1533 """
1537 self.csvargs = csvargs
1534 self.csvargs = csvargs
1538
1535
1539 def __xiter__(self, mode):
1536 def __xiter__(self, mode):
1540 input = self.input
1537 input = self.input
1541 if isinstance(input, ifile):
1538 if isinstance(input, ifile):
1542 input = input.open("rb")
1539 input = input.open("rb")
1543 reader = csv.reader(input, **self.csvargs)
1540 reader = csv.reader(input, **self.csvargs)
1544 for line in reader:
1541 for line in reader:
1545 yield List(line)
1542 yield List(line)
1546
1543
1547 def __xrepr__(self, mode):
1544 def __xrepr__(self, mode):
1548 yield (-1, False)
1545 yield (-1, False)
1549 if mode == "header" or mode == "footer":
1546 if mode == "header" or mode == "footer":
1550 input = getattr(self, "input", None)
1547 input = getattr(self, "input", None)
1551 if input is not None:
1548 if input is not None:
1552 for part in xrepr(input, mode):
1549 for part in xrepr(input, mode):
1553 yield part
1550 yield part
1554 yield (style_default, " | ")
1551 yield (style_default, " | ")
1555 yield (style_default, "%s(" % self.__class__.__name__)
1552 yield (style_default, "%s(" % self.__class__.__name__)
1556 for (i, (name, value)) in enumerate(self.csvargs.iteritems()):
1553 for (i, (name, value)) in enumerate(self.csvargs.iteritems()):
1557 if i:
1554 if i:
1558 yield (style_default, ", ")
1555 yield (style_default, ", ")
1559 yield (style_default, name)
1556 yield (style_default, name)
1560 yield (style_default, "=")
1557 yield (style_default, "=")
1561 for part in xrepr(value, "default"):
1558 for part in xrepr(value, "default"):
1562 yield part
1559 yield part
1563 yield (style_default, ")")
1560 yield (style_default, ")")
1564 else:
1561 else:
1565 yield (style_default, repr(self))
1562 yield (style_default, repr(self))
1566
1563
1567 def __repr__(self):
1564 def __repr__(self):
1568 args = ", ".join(["%s=%r" % item for item in self.csvargs.iteritems()])
1565 args = ", ".join(["%s=%r" % item for item in self.csvargs.iteritems()])
1569 return "<%s.%s %s at 0x%x>" % \
1566 return "<%s.%s %s at 0x%x>" % \
1570 (self.__class__.__module__, self.__class__.__name__, args, id(self))
1567 (self.__class__.__module__, self.__class__.__name__, args, id(self))
1571
1568
1572
1569
1573 class ix(Table):
1570 class ix(Table):
1574 """
1571 """
1575 Execute a system command and list its output as lines
1572 Execute a system command and list its output as lines
1576 (similar to ``os.popen()``).
1573 (similar to ``os.popen()``).
1577
1574
1578 Examples:
1575 Examples:
1579
1576
1580 >>> ix("ps x")
1577 >>> ix("ps x")
1581 >>> ix("find .") | ifile
1578 >>> ix("find .") | ifile
1582 """
1579 """
1583 def __init__(self, cmd):
1580 def __init__(self, cmd):
1584 self.cmd = cmd
1581 self.cmd = cmd
1585 self._pipe = None
1582 self._pipe = None
1586
1583
1587 def __xiter__(self, mode):
1584 def __xiter__(self, mode):
1588 self._pipe = os.popen(self.cmd)
1585 self._pipe = os.popen(self.cmd)
1589 for l in self._pipe:
1586 for l in self._pipe:
1590 yield l.rstrip("\r\n")
1587 yield l.rstrip("\r\n")
1591 self._pipe.close()
1588 self._pipe.close()
1592 self._pipe = None
1589 self._pipe = None
1593
1590
1594 def __del__(self):
1591 def __del__(self):
1595 if self._pipe is not None and not self._pipe.closed:
1592 if self._pipe is not None and not self._pipe.closed:
1596 self._pipe.close()
1593 self._pipe.close()
1597 self._pipe = None
1594 self._pipe = None
1598
1595
1599 def __xrepr__(self, mode):
1596 def __xrepr__(self, mode):
1600 yield (-1, True)
1597 yield (-1, True)
1601 if mode == "header" or mode == "footer":
1598 if mode == "header" or mode == "footer":
1602 yield (style_default, "%s(%r)" % (self.__class__.__name__, self.cmd))
1599 yield (style_default, "%s(%r)" % (self.__class__.__name__, self.cmd))
1603 else:
1600 else:
1604 yield (style_default, repr(self))
1601 yield (style_default, repr(self))
1605
1602
1606 def __repr__(self):
1603 def __repr__(self):
1607 return "%s.%s(%r)" % \
1604 return "%s.%s(%r)" % \
1608 (self.__class__.__module__, self.__class__.__name__, self.cmd)
1605 (self.__class__.__module__, self.__class__.__name__, self.cmd)
1609
1606
1610
1607
1611 class ifilter(Pipe):
1608 class ifilter(Pipe):
1612 """
1609 """
1613 Filter an input pipe. Only objects where an expression evaluates to true
1610 Filter an input pipe. Only objects where an expression evaluates to true
1614 (and doesn't raise an exception) are listed.
1611 (and doesn't raise an exception) are listed.
1615
1612
1616 Examples:
1613 Examples:
1617
1614
1618 >>> ils | ifilter("_.isfile() and size>1000")
1615 >>> ils | ifilter("_.isfile() and size>1000")
1619 >>> igrp | ifilter("len(mem)")
1616 >>> igrp | ifilter("len(mem)")
1620 >>> sys.modules | ifilter(lambda _:_.value is not None)
1617 >>> sys.modules | ifilter(lambda _:_.value is not None)
1621 """
1618 """
1622
1619
1623 def __init__(self, expr, errors="raiseifallfail"):
1620 def __init__(self, expr, errors="raiseifallfail"):
1624 """
1621 """
1625 Create an ``ifilter`` object. ``expr`` can be a callable or a string
1622 Create an ``ifilter`` object. ``expr`` can be a callable or a string
1626 containing an expression. ``errors`` specifies how exception during
1623 containing an expression. ``errors`` specifies how exception during
1627 evaluation of ``expr`` are handled:
1624 evaluation of ``expr`` are handled:
1628
1625
1629 * ``drop``: drop all items that have errors;
1626 * ``drop``: drop all items that have errors;
1630
1627
1631 * ``keep``: keep all items that have errors;
1628 * ``keep``: keep all items that have errors;
1632
1629
1633 * ``keeperror``: keep the exception of all items that have errors;
1630 * ``keeperror``: keep the exception of all items that have errors;
1634
1631
1635 * ``raise``: raise the exception;
1632 * ``raise``: raise the exception;
1636
1633
1637 * ``raiseifallfail``: raise the first exception if all items have errors;
1634 * ``raiseifallfail``: raise the first exception if all items have errors;
1638 otherwise drop those with errors (this is the default).
1635 otherwise drop those with errors (this is the default).
1639 """
1636 """
1640 self.expr = expr
1637 self.expr = expr
1641 self.errors = errors
1638 self.errors = errors
1642
1639
1643 def __xiter__(self, mode):
1640 def __xiter__(self, mode):
1644 if callable(self.expr):
1641 if callable(self.expr):
1645 def test(item):
1642 def test(item):
1646 return self.expr(item)
1643 return self.expr(item)
1647 else:
1644 else:
1648 def test(item):
1645 def test(item):
1649 return eval(self.expr, globals(), _AttrNamespace(item))
1646 return eval(self.expr, globals(), _AttrNamespace(item))
1650
1647
1651 ok = 0
1648 ok = 0
1652 exc_info = None
1649 exc_info = None
1653 for item in xiter(self.input, mode):
1650 for item in xiter(self.input, mode):
1654 try:
1651 try:
1655 if test(item):
1652 if test(item):
1656 yield item
1653 yield item
1657 ok += 1
1654 ok += 1
1658 except (KeyboardInterrupt, SystemExit):
1655 except (KeyboardInterrupt, SystemExit):
1659 raise
1656 raise
1660 except Exception, exc:
1657 except Exception, exc:
1661 if self.errors == "drop":
1658 if self.errors == "drop":
1662 pass # Ignore errors
1659 pass # Ignore errors
1663 elif self.errors == "keep":
1660 elif self.errors == "keep":
1664 yield item
1661 yield item
1665 elif self.errors == "keeperror":
1662 elif self.errors == "keeperror":
1666 yield exc
1663 yield exc
1667 elif self.errors == "raise":
1664 elif self.errors == "raise":
1668 raise
1665 raise
1669 elif self.errors == "raiseifallfail":
1666 elif self.errors == "raiseifallfail":
1670 if exc_info is None:
1667 if exc_info is None:
1671 exc_info = sys.exc_info()
1668 exc_info = sys.exc_info()
1672 if not ok and exc_info is not None:
1669 if not ok and exc_info is not None:
1673 raise exc_info[0], exc_info[1], exc_info[2]
1670 raise exc_info[0], exc_info[1], exc_info[2]
1674
1671
1675 def __xrepr__(self, mode):
1672 def __xrepr__(self, mode):
1676 yield (-1, True)
1673 yield (-1, True)
1677 if mode == "header" or mode == "footer":
1674 if mode == "header" or mode == "footer":
1678 input = getattr(self, "input", None)
1675 input = getattr(self, "input", None)
1679 if input is not None:
1676 if input is not None:
1680 for part in xrepr(input, mode):
1677 for part in xrepr(input, mode):
1681 yield part
1678 yield part
1682 yield (style_default, " | ")
1679 yield (style_default, " | ")
1683 yield (style_default, "%s(" % self.__class__.__name__)
1680 yield (style_default, "%s(" % self.__class__.__name__)
1684 for part in xrepr(self.expr, "default"):
1681 for part in xrepr(self.expr, "default"):
1685 yield part
1682 yield part
1686 yield (style_default, ")")
1683 yield (style_default, ")")
1687 else:
1684 else:
1688 yield (style_default, repr(self))
1685 yield (style_default, repr(self))
1689
1686
1690 def __repr__(self):
1687 def __repr__(self):
1691 return "<%s.%s expr=%r at 0x%x>" % \
1688 return "<%s.%s expr=%r at 0x%x>" % \
1692 (self.__class__.__module__, self.__class__.__name__,
1689 (self.__class__.__module__, self.__class__.__name__,
1693 self.expr, id(self))
1690 self.expr, id(self))
1694
1691
1695
1692
1696 class ieval(Pipe):
1693 class ieval(Pipe):
1697 """
1694 """
1698 Evaluate an expression for each object in the input pipe.
1695 Evaluate an expression for each object in the input pipe.
1699
1696
1700 Examples:
1697 Examples:
1701
1698
1702 >>> ils | ieval("_.abspath()")
1699 >>> ils | ieval("_.abspath()")
1703 >>> sys.path | ieval(ifile)
1700 >>> sys.path | ieval(ifile)
1704 """
1701 """
1705
1702
1706 def __init__(self, expr, errors="raiseifallfail"):
1703 def __init__(self, expr, errors="raiseifallfail"):
1707 """
1704 """
1708 Create an ``ieval`` object. ``expr`` can be a callable or a string
1705 Create an ``ieval`` object. ``expr`` can be a callable or a string
1709 containing an expression. For the meaning of ``errors`` see ``ifilter``.
1706 containing an expression. For the meaning of ``errors`` see ``ifilter``.
1710 """
1707 """
1711 self.expr = expr
1708 self.expr = expr
1712 self.errors = errors
1709 self.errors = errors
1713
1710
1714 def __xiter__(self, mode):
1711 def __xiter__(self, mode):
1715 if callable(self.expr):
1712 if callable(self.expr):
1716 def do(item):
1713 def do(item):
1717 return self.expr(item)
1714 return self.expr(item)
1718 else:
1715 else:
1719 def do(item):
1716 def do(item):
1720 return eval(self.expr, globals(), _AttrNamespace(item))
1717 return eval(self.expr, globals(), _AttrNamespace(item))
1721
1718
1722 ok = 0
1719 ok = 0
1723 exc_info = None
1720 exc_info = None
1724 for item in xiter(self.input, mode):
1721 for item in xiter(self.input, mode):
1725 try:
1722 try:
1726 yield do(item)
1723 yield do(item)
1727 except (KeyboardInterrupt, SystemExit):
1724 except (KeyboardInterrupt, SystemExit):
1728 raise
1725 raise
1729 except Exception, exc:
1726 except Exception, exc:
1730 if self.errors == "drop":
1727 if self.errors == "drop":
1731 pass # Ignore errors
1728 pass # Ignore errors
1732 elif self.errors == "keep":
1729 elif self.errors == "keep":
1733 yield item
1730 yield item
1734 elif self.errors == "keeperror":
1731 elif self.errors == "keeperror":
1735 yield exc
1732 yield exc
1736 elif self.errors == "raise":
1733 elif self.errors == "raise":
1737 raise
1734 raise
1738 elif self.errors == "raiseifallfail":
1735 elif self.errors == "raiseifallfail":
1739 if exc_info is None:
1736 if exc_info is None:
1740 exc_info = sys.exc_info()
1737 exc_info = sys.exc_info()
1741 if not ok and exc_info is not None:
1738 if not ok and exc_info is not None:
1742 raise exc_info[0], exc_info[1], exc_info[2]
1739 raise exc_info[0], exc_info[1], exc_info[2]
1743
1740
1744 def __xrepr__(self, mode):
1741 def __xrepr__(self, mode):
1745 yield (-1, True)
1742 yield (-1, True)
1746 if mode == "header" or mode == "footer":
1743 if mode == "header" or mode == "footer":
1747 input = getattr(self, "input", None)
1744 input = getattr(self, "input", None)
1748 if input is not None:
1745 if input is not None:
1749 for part in xrepr(input, mode):
1746 for part in xrepr(input, mode):
1750 yield part
1747 yield part
1751 yield (style_default, " | ")
1748 yield (style_default, " | ")
1752 yield (style_default, "%s(" % self.__class__.__name__)
1749 yield (style_default, "%s(" % self.__class__.__name__)
1753 for part in xrepr(self.expr, "default"):
1750 for part in xrepr(self.expr, "default"):
1754 yield part
1751 yield part
1755 yield (style_default, ")")
1752 yield (style_default, ")")
1756 else:
1753 else:
1757 yield (style_default, repr(self))
1754 yield (style_default, repr(self))
1758
1755
1759 def __repr__(self):
1756 def __repr__(self):
1760 return "<%s.%s expr=%r at 0x%x>" % \
1757 return "<%s.%s expr=%r at 0x%x>" % \
1761 (self.__class__.__module__, self.__class__.__name__,
1758 (self.__class__.__module__, self.__class__.__name__,
1762 self.expr, id(self))
1759 self.expr, id(self))
1763
1760
1764
1761
1765 class ienum(Pipe):
1762 class ienum(Pipe):
1766 """
1763 """
1767 Enumerate the input pipe (i.e. wrap each input object in an object
1764 Enumerate the input pipe (i.e. wrap each input object in an object
1768 with ``index`` and ``object`` attributes).
1765 with ``index`` and ``object`` attributes).
1769
1766
1770 Examples:
1767 Examples:
1771
1768
1772 >>> xrange(20) | ieval("_,_*_") | ienum | ifilter("index % 2 == 0") | ieval("object")
1769 >>> xrange(20) | ieval("_,_*_") | ienum | ifilter("index % 2 == 0") | ieval("object")
1773 """
1770 """
1774 def __xiter__(self, mode):
1771 def __xiter__(self, mode):
1775 fields = ("index", "object")
1772 fields = ("index", "object")
1776 for (index, object) in enumerate(xiter(self.input, mode)):
1773 for (index, object) in enumerate(xiter(self.input, mode)):
1777 yield Fields(fields, index=index, object=object)
1774 yield Fields(fields, index=index, object=object)
1778
1775
1779
1776
1780 class isort(Pipe):
1777 class isort(Pipe):
1781 """
1778 """
1782 Sorts the input pipe.
1779 Sorts the input pipe.
1783
1780
1784 Examples:
1781 Examples:
1785
1782
1786 >>> ils | isort("size")
1783 >>> ils | isort("size")
1787 >>> ils | isort("_.isdir(), _.lower()", reverse=True)
1784 >>> ils | isort("_.isdir(), _.lower()", reverse=True)
1788 """
1785 """
1789
1786
1790 def __init__(self, key, reverse=False):
1787 def __init__(self, key, reverse=False):
1791 """
1788 """
1792 Create an ``isort`` object. ``key`` can be a callable or a string
1789 Create an ``isort`` object. ``key`` can be a callable or a string
1793 containing an expression. If ``reverse`` is true the sort order will
1790 containing an expression. If ``reverse`` is true the sort order will
1794 be reversed.
1791 be reversed.
1795 """
1792 """
1796 self.key = key
1793 self.key = key
1797 self.reverse = reverse
1794 self.reverse = reverse
1798
1795
1799 def __xiter__(self, mode):
1796 def __xiter__(self, mode):
1800 if callable(self.key):
1797 if callable(self.key):
1801 items = sorted(
1798 items = sorted(
1802 xiter(self.input, mode),
1799 xiter(self.input, mode),
1803 key=self.key,
1800 key=self.key,
1804 reverse=self.reverse
1801 reverse=self.reverse
1805 )
1802 )
1806 else:
1803 else:
1807 def key(item):
1804 def key(item):
1808 return eval(self.key, globals(), _AttrNamespace(item))
1805 return eval(self.key, globals(), _AttrNamespace(item))
1809 items = sorted(
1806 items = sorted(
1810 xiter(self.input, mode),
1807 xiter(self.input, mode),
1811 key=key,
1808 key=key,
1812 reverse=self.reverse
1809 reverse=self.reverse
1813 )
1810 )
1814 for item in items:
1811 for item in items:
1815 yield item
1812 yield item
1816
1813
1817 def __xrepr__(self, mode):
1814 def __xrepr__(self, mode):
1818 yield (-1, True)
1815 yield (-1, True)
1819 if mode == "header" or mode == "footer":
1816 if mode == "header" or mode == "footer":
1820 input = getattr(self, "input", None)
1817 input = getattr(self, "input", None)
1821 if input is not None:
1818 if input is not None:
1822 for part in xrepr(input, mode):
1819 for part in xrepr(input, mode):
1823 yield part
1820 yield part
1824 yield (style_default, " | ")
1821 yield (style_default, " | ")
1825 yield (style_default, "%s(" % self.__class__.__name__)
1822 yield (style_default, "%s(" % self.__class__.__name__)
1826 for part in xrepr(self.key, "default"):
1823 for part in xrepr(self.key, "default"):
1827 yield part
1824 yield part
1828 if self.reverse:
1825 if self.reverse:
1829 yield (style_default, ", ")
1826 yield (style_default, ", ")
1830 for part in xrepr(True, "default"):
1827 for part in xrepr(True, "default"):
1831 yield part
1828 yield part
1832 yield (style_default, ")")
1829 yield (style_default, ")")
1833 else:
1830 else:
1834 yield (style_default, repr(self))
1831 yield (style_default, repr(self))
1835
1832
1836 def __repr__(self):
1833 def __repr__(self):
1837 return "<%s.%s key=%r reverse=%r at 0x%x>" % \
1834 return "<%s.%s key=%r reverse=%r at 0x%x>" % \
1838 (self.__class__.__module__, self.__class__.__name__,
1835 (self.__class__.__module__, self.__class__.__name__,
1839 self.key, self.reverse, id(self))
1836 self.key, self.reverse, id(self))
1840
1837
1841
1838
1842 tab = 3 # for expandtabs()
1839 tab = 3 # for expandtabs()
1843
1840
1844 def _format(field):
1841 def _format(field):
1845 if isinstance(field, str):
1842 if isinstance(field, str):
1846 text = repr(field.expandtabs(tab))[1:-1]
1843 text = repr(field.expandtabs(tab))[1:-1]
1847 elif isinstance(field, unicode):
1844 elif isinstance(field, unicode):
1848 text = repr(field.expandtabs(tab))[2:-1]
1845 text = repr(field.expandtabs(tab))[2:-1]
1849 elif isinstance(field, datetime.datetime):
1846 elif isinstance(field, datetime.datetime):
1850 # Don't use strftime() here, as this requires year >= 1900
1847 # Don't use strftime() here, as this requires year >= 1900
1851 text = "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
1848 text = "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
1852 (field.year, field.month, field.day,
1849 (field.year, field.month, field.day,
1853 field.hour, field.minute, field.second, field.microsecond)
1850 field.hour, field.minute, field.second, field.microsecond)
1854 elif isinstance(field, datetime.date):
1851 elif isinstance(field, datetime.date):
1855 text = "%04d-%02d-%02d" % (field.year, field.month, field.day)
1852 text = "%04d-%02d-%02d" % (field.year, field.month, field.day)
1856 else:
1853 else:
1857 text = repr(field)
1854 text = repr(field)
1858 return text
1855 return text
1859
1856
1860
1857
1861 class Display(object):
1858 class Display(object):
1862 class __metaclass__(type):
1859 class __metaclass__(type):
1863 def __ror__(self, input):
1860 def __ror__(self, input):
1864 return input | self()
1861 return input | self()
1865
1862
1866 def __ror__(self, input):
1863 def __ror__(self, input):
1867 self.input = input
1864 self.input = input
1868 return self
1865 return self
1869
1866
1870 def display(self):
1867 def display(self):
1871 pass
1868 pass
1872
1869
1873
1870
1874 class iless(Display):
1871 class iless(Display):
1875 cmd = "less --quit-if-one-screen --LONG-PROMPT --LINE-NUMBERS --chop-long-lines --shift=8 --RAW-CONTROL-CHARS"
1872 cmd = "less --quit-if-one-screen --LONG-PROMPT --LINE-NUMBERS --chop-long-lines --shift=8 --RAW-CONTROL-CHARS"
1876
1873
1877 def display(self):
1874 def display(self):
1878 try:
1875 try:
1879 pager = os.popen(self.cmd, "w")
1876 pager = os.popen(self.cmd, "w")
1880 try:
1877 try:
1881 for item in xiter(self.input, "default"):
1878 for item in xiter(self.input, "default"):
1882 attrs = xattrs(item, "default")
1879 attrs = xattrs(item, "default")
1883 attrs = ["%s=%s" % (a, _format(_getattr(item, a))) for a in attrs]
1880 attrs = ["%s=%s" % (a, _format(_getattr(item, a))) for a in attrs]
1884 pager.write(" ".join(attrs))
1881 pager.write(" ".join(attrs))
1885 pager.write("\n")
1882 pager.write("\n")
1886 finally:
1883 finally:
1887 pager.close()
1884 pager.close()
1888 except Exception, exc:
1885 except Exception, exc:
1889 print "%s: %s" % (exc.__class__.__name__, str(exc))
1886 print "%s: %s" % (exc.__class__.__name__, str(exc))
1890
1887
1891
1888
1892 def xformat(value, mode, maxlength):
1889 def xformat(value, mode, maxlength):
1893 align = None
1890 align = None
1894 full = False
1891 full = False
1895 width = 0
1892 width = 0
1896 text = Text()
1893 text = Text()
1897 for part in xrepr(value, mode):
1894 for part in xrepr(value, mode):
1898 # part is (alignment, stop)
1895 # part is (alignment, stop)
1899 if isinstance(part[0], int):
1896 if isinstance(part[0], int):
1900 # only consider the first occurence
1897 # only consider the first occurence
1901 if align is None:
1898 if align is None:
1902 align = part[0]
1899 align = part[0]
1903 full = part[1]
1900 full = part[1]
1904 # part is (style, text)
1901 # part is (style, text)
1905 else:
1902 else:
1906 text.append(part)
1903 text.append(part)
1907 width += len(part[1])
1904 width += len(part[1])
1908 if width >= maxlength and not full:
1905 if width >= maxlength and not full:
1909 text.append((style_ellisis, "..."))
1906 text.append((style_ellisis, "..."))
1910 width += 3
1907 width += 3
1911 break
1908 break
1912 if align is None: # default to left alignment
1909 if align is None: # default to left alignment
1913 align = -1
1910 align = -1
1914 return (align, width, text)
1911 return (align, width, text)
1915
1912
1916
1913
1917 class idump(Display):
1914 class idump(Display):
1918 # The approximate maximum length of a column entry
1915 # The approximate maximum length of a column entry
1919 maxattrlength = 200
1916 maxattrlength = 200
1920
1917
1921 # Style for column names
1918 # Style for column names
1922 style_header = Style(COLOR_WHITE, COLOR_BLACK, A_BOLD)
1919 style_header = Style(COLOR_WHITE, COLOR_BLACK, A_BOLD)
1923
1920
1924 def __init__(self, *attrs):
1921 def __init__(self, *attrs):
1925 self.attrs = attrs
1922 self.attrs = attrs
1926 self.headerpadchar = " "
1923 self.headerpadchar = " "
1927 self.headersepchar = "|"
1924 self.headersepchar = "|"
1928 self.datapadchar = " "
1925 self.datapadchar = " "
1929 self.datasepchar = "|"
1926 self.datasepchar = "|"
1930
1927
1931 def display(self):
1928 def display(self):
1932 stream = genutils.Term.cout
1929 stream = genutils.Term.cout
1933 allattrs = []
1930 allattrs = []
1934 allattrset = set()
1931 allattrset = set()
1935 colwidths = {}
1932 colwidths = {}
1936 rows = []
1933 rows = []
1937 for item in xiter(self.input, "default"):
1934 for item in xiter(self.input, "default"):
1938 row = {}
1935 row = {}
1939 attrs = self.attrs
1936 attrs = self.attrs
1940 if not attrs:
1937 if not attrs:
1941 attrs = xattrs(item, "default")
1938 attrs = xattrs(item, "default")
1942 for attrname in attrs:
1939 for attrname in attrs:
1943 if attrname not in allattrset:
1940 if attrname not in allattrset:
1944 allattrs.append(attrname)
1941 allattrs.append(attrname)
1945 allattrset.add(attrname)
1942 allattrset.add(attrname)
1946 colwidths[attrname] = len(_attrname(attrname))
1943 colwidths[attrname] = len(_attrname(attrname))
1947 try:
1944 try:
1948 value = _getattr(item, attrname, None)
1945 value = _getattr(item, attrname, None)
1949 except (KeyboardInterrupt, SystemExit):
1946 except (KeyboardInterrupt, SystemExit):
1950 raise
1947 raise
1951 except Exception, exc:
1948 except Exception, exc:
1952 value = exc
1949 value = exc
1953 (align, width, text) = xformat(value, "cell", self.maxattrlength)
1950 (align, width, text) = xformat(value, "cell", self.maxattrlength)
1954 colwidths[attrname] = max(colwidths[attrname], width)
1951 colwidths[attrname] = max(colwidths[attrname], width)
1955 # remember alignment, length and colored parts
1952 # remember alignment, length and colored parts
1956 row[attrname] = (align, width, text)
1953 row[attrname] = (align, width, text)
1957 rows.append(row)
1954 rows.append(row)
1958
1955
1959 stream.write("\n")
1956 stream.write("\n")
1960 for (i, attrname) in enumerate(allattrs):
1957 for (i, attrname) in enumerate(allattrs):
1961 self.style_header(_attrname(attrname)).write(stream)
1958 self.style_header(_attrname(attrname)).write(stream)
1962 spc = colwidths[attrname] - len(_attrname(attrname))
1959 spc = colwidths[attrname] - len(_attrname(attrname))
1963 if i < len(colwidths)-1:
1960 if i < len(colwidths)-1:
1964 stream.write(self.headerpadchar*spc)
1961 stream.write(self.headerpadchar*spc)
1965 stream.write(self.headersepchar)
1962 stream.write(self.headersepchar)
1966 stream.write("\n")
1963 stream.write("\n")
1967
1964
1968 for row in rows:
1965 for row in rows:
1969 for (i, attrname) in enumerate(allattrs):
1966 for (i, attrname) in enumerate(allattrs):
1970 (align, width, text) = row[attrname]
1967 (align, width, text) = row[attrname]
1971 spc = colwidths[attrname] - width
1968 spc = colwidths[attrname] - width
1972 if align == -1:
1969 if align == -1:
1973 text.write(stream)
1970 text.write(stream)
1974 if i < len(colwidths)-1:
1971 if i < len(colwidths)-1:
1975 stream.write(self.datapadchar*spc)
1972 stream.write(self.datapadchar*spc)
1976 elif align == 0:
1973 elif align == 0:
1977 spc = colwidths[attrname] - width
1974 spc = colwidths[attrname] - width
1978 spc1 = spc//2
1975 spc1 = spc//2
1979 spc2 = spc-spc1
1976 spc2 = spc-spc1
1980 stream.write(self.datapadchar*spc1)
1977 stream.write(self.datapadchar*spc1)
1981 text.write(stream)
1978 text.write(stream)
1982 if i < len(colwidths)-1:
1979 if i < len(colwidths)-1:
1983 stream.write(self.datapadchar*spc2)
1980 stream.write(self.datapadchar*spc2)
1984 else:
1981 else:
1985 stream.write(self.datapadchar*spc)
1982 stream.write(self.datapadchar*spc)
1986 text.write(stream)
1983 text.write(stream)
1987 if i < len(colwidths)-1:
1984 if i < len(colwidths)-1:
1988 stream.write(self.datasepchar)
1985 stream.write(self.datasepchar)
1989 stream.write("\n")
1986 stream.write("\n")
1990
1987
1991
1988
1992 class XMode(object):
1989 class XMode(object):
1993 """
1990 """
1994 An ``XMode`` object describes one enter mode available for an object
1991 An ``XMode`` object describes one enter mode available for an object
1995 """
1992 """
1996 def __init__(self, object, mode, title=None, description=None):
1993 def __init__(self, object, mode, title=None, description=None):
1997 """
1994 """
1998 Create a new ``XMode`` object for the object ``object``. This object
1995 Create a new ``XMode`` object for the object ``object``. This object
1999 must support the enter mode ``mode`` (i.e. ``object.__xiter__(mode)``
1996 must support the enter mode ``mode`` (i.e. ``object.__xiter__(mode)``
2000 must return an iterable). ``title`` and ``description`` will be
1997 must return an iterable). ``title`` and ``description`` will be
2001 displayed in the browser when selecting among the available modes.
1998 displayed in the browser when selecting among the available modes.
2002 """
1999 """
2003 self.object = object
2000 self.object = object
2004 self.mode = mode
2001 self.mode = mode
2005 self.title = title
2002 self.title = title
2006 self.description = description
2003 self.description = description
2007
2004
2008 def __repr__(self):
2005 def __repr__(self):
2009 return "<%s.%s object mode=%r at 0x%x>" % \
2006 return "<%s.%s object mode=%r at 0x%x>" % \
2010 (self.__class__.__module__, self.__class__.__name__,
2007 (self.__class__.__module__, self.__class__.__name__,
2011 self.mode, id(self))
2008 self.mode, id(self))
2012
2009
2013 def __xrepr__(self, mode):
2010 def __xrepr__(self, mode):
2014 yield (-1, True)
2011 yield (-1, True)
2015 if mode == "header" or mode == "footer":
2012 if mode == "header" or mode == "footer":
2016 yield (style_default, self.title)
2013 yield (style_default, self.title)
2017 else:
2014 else:
2018 yield (style_default, repr(self))
2015 yield (style_default, repr(self))
2019
2016
2020 def __xattrs__(self, mode):
2017 def __xattrs__(self, mode):
2021 if mode == "detail":
2018 if mode == "detail":
2022 return ("object", "mode", "title", "description")
2019 return ("object", "mode", "title", "description")
2023 return ("title", "description")
2020 return ("title", "description")
2024
2021
2025 def __xiter__(self, mode):
2022 def __xiter__(self, mode):
2026 return xiter(self.object, self.mode)
2023 return xiter(self.object, self.mode)
2027
2024
2028
2025
2029 class XAttr(object):
2026 class XAttr(object):
2030 def __init__(self, object, name):
2027 def __init__(self, object, name):
2031 self.name = _attrname(name)
2028 self.name = _attrname(name)
2032
2029
2033 try:
2030 try:
2034 self.value = _getattr(object, name)
2031 self.value = _getattr(object, name)
2035 except (KeyboardInterrupt, SystemExit):
2032 except (KeyboardInterrupt, SystemExit):
2036 raise
2033 raise
2037 except Exception, exc:
2034 except Exception, exc:
2038 if exc.__class__.__module__ == "exceptions":
2035 if exc.__class__.__module__ == "exceptions":
2039 self.value = exc.__class__.__name__
2036 self.value = exc.__class__.__name__
2040 else:
2037 else:
2041 self.value = "%s.%s" % \
2038 self.value = "%s.%s" % \
2042 (exc.__class__.__module__, exc.__class__.__name__)
2039 (exc.__class__.__module__, exc.__class__.__name__)
2043 self.type = self.value
2040 self.type = self.value
2044 else:
2041 else:
2045 t = type(self.value)
2042 t = type(self.value)
2046 if t.__module__ == "__builtin__":
2043 if t.__module__ == "__builtin__":
2047 self.type = t.__name__
2044 self.type = t.__name__
2048 else:
2045 else:
2049 self.type = "%s.%s" % (t.__module__, t.__name__)
2046 self.type = "%s.%s" % (t.__module__, t.__name__)
2050
2047
2051 doc = None
2048 doc = None
2052 if isinstance(name, basestring):
2049 if isinstance(name, basestring):
2053 if name.endswith("()"):
2050 if name.endswith("()"):
2054 doc = getattr(getattr(object, name[:-2]), "__doc__", None)
2051 doc = getattr(getattr(object, name[:-2]), "__doc__", None)
2055 else:
2052 else:
2056 try:
2053 try:
2057 meta = getattr(type(object), name)
2054 meta = getattr(type(object), name)
2058 except AttributeError:
2055 except AttributeError:
2059 pass
2056 pass
2060 else:
2057 else:
2061 if isinstance(meta, property):
2058 if isinstance(meta, property):
2062 doc = getattr(meta, "__doc__", None)
2059 doc = getattr(meta, "__doc__", None)
2063 elif callable(name):
2060 elif callable(name):
2064 doc = getattr(name, "__doc__", None)
2061 doc = getattr(name, "__doc__", None)
2065 if isinstance(doc, basestring):
2062 if isinstance(doc, basestring):
2066 doc = doc.strip()
2063 doc = doc.strip()
2067 self.doc = doc
2064 self.doc = doc
2068
2065
2069 def __xattrs__(self, mode):
2066 def __xattrs__(self, mode):
2070 return ("name", "type", "doc", "value")
2067 return ("name", "type", "doc", "value")
2071
2068
2072
2069
2073 _ibrowse_help = """
2070 _ibrowse_help = """
2074 down
2071 down
2075 Move the cursor to the next line.
2072 Move the cursor to the next line.
2076
2073
2077 up
2074 up
2078 Move the cursor to the previous line.
2075 Move the cursor to the previous line.
2079
2076
2080 pagedown
2077 pagedown
2081 Move the cursor down one page (minus overlap).
2078 Move the cursor down one page (minus overlap).
2082
2079
2083 pageup
2080 pageup
2084 Move the cursor up one page (minus overlap).
2081 Move the cursor up one page (minus overlap).
2085
2082
2086 left
2083 left
2087 Move the cursor left.
2084 Move the cursor left.
2088
2085
2089 right
2086 right
2090 Move the cursor right.
2087 Move the cursor right.
2091
2088
2092 home
2089 home
2093 Move the cursor to the first column.
2090 Move the cursor to the first column.
2094
2091
2095 end
2092 end
2096 Move the cursor to the last column.
2093 Move the cursor to the last column.
2097
2094
2098 prevattr
2095 prevattr
2099 Move the cursor one attribute column to the left.
2096 Move the cursor one attribute column to the left.
2100
2097
2101 nextattr
2098 nextattr
2102 Move the cursor one attribute column to the right.
2099 Move the cursor one attribute column to the right.
2103
2100
2104 pick
2101 pick
2105 'Pick' the object under the cursor (i.e. the row the cursor is on). This
2102 'Pick' the object under the cursor (i.e. the row the cursor is on). This
2106 leaves the browser and returns the picked object to the caller. (In IPython
2103 leaves the browser and returns the picked object to the caller. (In IPython
2107 this object will be available as the '_' variable.)
2104 this object will be available as the '_' variable.)
2108
2105
2109 pickattr
2106 pickattr
2110 'Pick' the attribute under the cursor (i.e. the row/column the cursor is on).
2107 'Pick' the attribute under the cursor (i.e. the row/column the cursor is on).
2111
2108
2112 pickallattrs
2109 pickallattrs
2113 Pick' the complete column under the cursor (i.e. the attribute under the
2110 Pick' the complete column under the cursor (i.e. the attribute under the
2114 cursor) from all currently fetched objects. These attributes will be returned
2111 cursor) from all currently fetched objects. These attributes will be returned
2115 as a list.
2112 as a list.
2116
2113
2117 tooglemark
2114 tooglemark
2118 Mark/unmark the object under the cursor. Marked objects have a '!' after the
2115 Mark/unmark the object under the cursor. Marked objects have a '!' after the
2119 row number).
2116 row number).
2120
2117
2121 pickmarked
2118 pickmarked
2122 'Pick' marked objects. Marked objects will be returned as a list.
2119 'Pick' marked objects. Marked objects will be returned as a list.
2123
2120
2124 pickmarkedattr
2121 pickmarkedattr
2125 'Pick' the attribute under the cursor from all marked objects (This returns a
2122 'Pick' the attribute under the cursor from all marked objects (This returns a
2126 list).
2123 list).
2127
2124
2128 enterdefault
2125 enterdefault
2129 Enter the object under the cursor. (what this mean depends on the object
2126 Enter the object under the cursor. (what this mean depends on the object
2130 itself (i.e. how it implements the '__xiter__' method). This opens a new
2127 itself (i.e. how it implements the '__xiter__' method). This opens a new
2131 browser 'level'.
2128 browser 'level'.
2132
2129
2133 enter
2130 enter
2134 Enter the object under the cursor. If the object provides different enter
2131 Enter the object under the cursor. If the object provides different enter
2135 modes a menu of all modes will be presented; choose one and enter it (via the
2132 modes a menu of all modes will be presented; choose one and enter it (via the
2136 'enter' or 'enterdefault' command).
2133 'enter' or 'enterdefault' command).
2137
2134
2138 enterattr
2135 enterattr
2139 Enter the attribute under the cursor.
2136 Enter the attribute under the cursor.
2140
2137
2141 leave
2138 leave
2142 Leave the current browser level and go back to the previous one.
2139 Leave the current browser level and go back to the previous one.
2143
2140
2144 detail
2141 detail
2145 Show a detail view of the object under the cursor. This shows the name, type,
2142 Show a detail view of the object under the cursor. This shows the name, type,
2146 doc string and value of the object attributes (and it might show more
2143 doc string and value of the object attributes (and it might show more
2147 attributes than in the list view, depending on the object).
2144 attributes than in the list view, depending on the object).
2148
2145
2149 detailattr
2146 detailattr
2150 Show a detail view of the attribute under the cursor.
2147 Show a detail view of the attribute under the cursor.
2151
2148
2152 markrange
2149 markrange
2153 Mark all objects from the last marked object before the current cursor
2150 Mark all objects from the last marked object before the current cursor
2154 position to the cursor position.
2151 position to the cursor position.
2155
2152
2156 sortattrasc
2153 sortattrasc
2157 Sort the objects (in ascending order) using the attribute under the cursor as
2154 Sort the objects (in ascending order) using the attribute under the cursor as
2158 the sort key.
2155 the sort key.
2159
2156
2160 sortattrdesc
2157 sortattrdesc
2161 Sort the objects (in descending order) using the attribute under the cursor as
2158 Sort the objects (in descending order) using the attribute under the cursor as
2162 the sort key.
2159 the sort key.
2163
2160
2164 goto
2161 goto
2165 Jump to a row. The row number can be entered at the bottom of the screen.
2162 Jump to a row. The row number can be entered at the bottom of the screen.
2166
2163
2167 find
2164 find
2168 Search forward for a row. At the bottom of the screen the condition can be
2165 Search forward for a row. At the bottom of the screen the condition can be
2169 entered.
2166 entered.
2170
2167
2171 findbackwards
2168 findbackwards
2172 Search backward for a row. At the bottom of the screen the condition can be
2169 Search backward for a row. At the bottom of the screen the condition can be
2173 entered.
2170 entered.
2174
2171
2175 help
2172 help
2176 This screen.
2173 This screen.
2177 """
2174 """
2178
2175
2179
2176
2180 if curses is not None:
2177 if curses is not None:
2181 class UnassignedKeyError(Exception):
2178 class UnassignedKeyError(Exception):
2182 """
2179 """
2183 Exception that is used for reporting unassigned keys.
2180 Exception that is used for reporting unassigned keys.
2184 """
2181 """
2185
2182
2186
2183
2187 class UnknownCommandError(Exception):
2184 class UnknownCommandError(Exception):
2188 """
2185 """
2189 Exception that is used for reporting unknown command (this should never
2186 Exception that is used for reporting unknown command (this should never
2190 happen).
2187 happen).
2191 """
2188 """
2192
2189
2193
2190
2194 class CommandError(Exception):
2191 class CommandError(Exception):
2195 """
2192 """
2196 Exception that is used for reporting that a command can't be executed.
2193 Exception that is used for reporting that a command can't be executed.
2197 """
2194 """
2198
2195
2199
2196
2200 class _BrowserCachedItem(object):
2197 class _BrowserCachedItem(object):
2201 # This is used internally by ``ibrowse`` to store a item together with its
2198 # This is used internally by ``ibrowse`` to store a item together with its
2202 # marked status.
2199 # marked status.
2203 __slots__ = ("item", "marked")
2200 __slots__ = ("item", "marked")
2204
2201
2205 def __init__(self, item):
2202 def __init__(self, item):
2206 self.item = item
2203 self.item = item
2207 self.marked = False
2204 self.marked = False
2208
2205
2209
2206
2210 class _BrowserHelp(object):
2207 class _BrowserHelp(object):
2211 style_header = Style(COLOR_RED, COLOR_BLACK)
2208 style_header = Style(COLOR_RED, COLOR_BLACK)
2212 # This is used internally by ``ibrowse`` for displaying the help screen.
2209 # This is used internally by ``ibrowse`` for displaying the help screen.
2213 def __init__(self, browser):
2210 def __init__(self, browser):
2214 self.browser = browser
2211 self.browser = browser
2215
2212
2216 def __xrepr__(self, mode):
2213 def __xrepr__(self, mode):
2217 yield (-1, True)
2214 yield (-1, True)
2218 if mode == "header" or mode == "footer":
2215 if mode == "header" or mode == "footer":
2219 yield (style_default, "ibrowse help screen")
2216 yield (style_default, "ibrowse help screen")
2220 else:
2217 else:
2221 yield (style_default, repr(self))
2218 yield (style_default, repr(self))
2222
2219
2223 def __xiter__(self, mode):
2220 def __xiter__(self, mode):
2224 # Get reverse key mapping
2221 # Get reverse key mapping
2225 allkeys = {}
2222 allkeys = {}
2226 for (key, cmd) in self.browser.keymap.iteritems():
2223 for (key, cmd) in self.browser.keymap.iteritems():
2227 allkeys.setdefault(cmd, []).append(key)
2224 allkeys.setdefault(cmd, []).append(key)
2228
2225
2229 fields = ("key", "description")
2226 fields = ("key", "description")
2230
2227
2231 for (i, command) in enumerate(_ibrowse_help.strip().split("\n\n")):
2228 for (i, command) in enumerate(_ibrowse_help.strip().split("\n\n")):
2232 if i:
2229 if i:
2233 yield Fields(fields, key="", description="")
2230 yield Fields(fields, key="", description="")
2234
2231
2235 (name, description) = command.split("\n", 1)
2232 (name, description) = command.split("\n", 1)
2236 keys = allkeys.get(name, [])
2233 keys = allkeys.get(name, [])
2237 lines = textwrap.wrap(description, 60)
2234 lines = textwrap.wrap(description, 60)
2238
2235
2239 yield Fields(fields, description=Text((self.style_header, name)))
2236 yield Fields(fields, description=Text((self.style_header, name)))
2240 for i in xrange(max(len(keys), len(lines))):
2237 for i in xrange(max(len(keys), len(lines))):
2241 try:
2238 try:
2242 key = self.browser.keylabel(keys[i])
2239 key = self.browser.keylabel(keys[i])
2243 except IndexError:
2240 except IndexError:
2244 key = ""
2241 key = ""
2245 try:
2242 try:
2246 line = lines[i]
2243 line = lines[i]
2247 except IndexError:
2244 except IndexError:
2248 line = ""
2245 line = ""
2249 yield Fields(fields, key=key, description=line)
2246 yield Fields(fields, key=key, description=line)
2250
2247
2251
2248
2252 class _BrowserLevel(object):
2249 class _BrowserLevel(object):
2253 # This is used internally to store the state (iterator, fetch items,
2250 # This is used internally to store the state (iterator, fetch items,
2254 # position of cursor and screen, etc.) of one browser level
2251 # position of cursor and screen, etc.) of one browser level
2255 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
2252 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
2256 # a stack.
2253 # a stack.
2257 def __init__(self, browser, input, iterator, mainsizey, *attrs):
2254 def __init__(self, browser, input, iterator, mainsizey, *attrs):
2258 self.browser = browser
2255 self.browser = browser
2259 self.input = input
2256 self.input = input
2260 self.header = [x for x in xrepr(input, "header") if not isinstance(x[0], int)]
2257 self.header = [x for x in xrepr(input, "header") if not isinstance(x[0], int)]
2261 # iterator for the input
2258 # iterator for the input
2262 self.iterator = iterator
2259 self.iterator = iterator
2263
2260
2264 # is the iterator exhausted?
2261 # is the iterator exhausted?
2265 self.exhausted = False
2262 self.exhausted = False
2266
2263
2267 # attributes to be display (autodetected if empty)
2264 # attributes to be display (autodetected if empty)
2268 self.attrs = attrs
2265 self.attrs = attrs
2269
2266
2270 # fetched items (+ marked flag)
2267 # fetched items (+ marked flag)
2271 self.items = deque()
2268 self.items = deque()
2272
2269
2273 # Number of marked objects
2270 # Number of marked objects
2274 self.marked = 0
2271 self.marked = 0
2275
2272
2276 # Vertical cursor position
2273 # Vertical cursor position
2277 self.cury = 0
2274 self.cury = 0
2278
2275
2279 # Horizontal cursor position
2276 # Horizontal cursor position
2280 self.curx = 0
2277 self.curx = 0
2281
2278
2282 # Index of first data column
2279 # Index of first data column
2283 self.datastartx = 0
2280 self.datastartx = 0
2284
2281
2285 # Index of first data line
2282 # Index of first data line
2286 self.datastarty = 0
2283 self.datastarty = 0
2287
2284
2288 # height of the data display area
2285 # height of the data display area
2289 self.mainsizey = mainsizey
2286 self.mainsizey = mainsizey
2290
2287
2291 # width of the data display area (changes when scrolling)
2288 # width of the data display area (changes when scrolling)
2292 self.mainsizex = 0
2289 self.mainsizex = 0
2293
2290
2294 # Size of row number (changes when scrolling)
2291 # Size of row number (changes when scrolling)
2295 self.numbersizex = 0
2292 self.numbersizex = 0
2296
2293
2297 # Attribute names to display (in this order)
2294 # Attribute names to display (in this order)
2298 self.displayattrs = []
2295 self.displayattrs = []
2299
2296
2300 # index and name of attribute under the cursor
2297 # index and name of attribute under the cursor
2301 self.displayattr = (None, _default)
2298 self.displayattr = (None, _default)
2302
2299
2303 # Maps attribute names to column widths
2300 # Maps attribute names to column widths
2304 self.colwidths = {}
2301 self.colwidths = {}
2305
2302
2306 self.fetch(mainsizey)
2303 self.fetch(mainsizey)
2307 self.calcdisplayattrs()
2304 self.calcdisplayattrs()
2308 # formatted attributes for the items on screen
2305 # formatted attributes for the items on screen
2309 # (i.e. self.items[self.datastarty:self.datastarty+self.mainsizey])
2306 # (i.e. self.items[self.datastarty:self.datastarty+self.mainsizey])
2310 self.displayrows = [self.getrow(i) for i in xrange(len(self.items))]
2307 self.displayrows = [self.getrow(i) for i in xrange(len(self.items))]
2311 self.calcwidths()
2308 self.calcwidths()
2312 self.calcdisplayattr()
2309 self.calcdisplayattr()
2313
2310
2314 def fetch(self, count):
2311 def fetch(self, count):
2315 # Try to fill ``self.items`` with at least ``count`` objects.
2312 # Try to fill ``self.items`` with at least ``count`` objects.
2316 have = len(self.items)
2313 have = len(self.items)
2317 while not self.exhausted and have < count:
2314 while not self.exhausted and have < count:
2318 try:
2315 try:
2319 item = self.iterator.next()
2316 item = self.iterator.next()
2320 except StopIteration:
2317 except StopIteration:
2321 self.exhausted = True
2318 self.exhausted = True
2322 break
2319 break
2323 else:
2320 else:
2324 have += 1
2321 have += 1
2325 self.items.append(_BrowserCachedItem(item))
2322 self.items.append(_BrowserCachedItem(item))
2326
2323
2327 def calcdisplayattrs(self):
2324 def calcdisplayattrs(self):
2328 # Calculate which attributes are available from the objects that are
2325 # Calculate which attributes are available from the objects that are
2329 # currently visible on screen (and store it in ``self.displayattrs``)
2326 # currently visible on screen (and store it in ``self.displayattrs``)
2330 attrnames = set()
2327 attrnames = set()
2331 # If the browser object specifies a fixed list of attributes,
2328 # If the browser object specifies a fixed list of attributes,
2332 # simply use it.
2329 # simply use it.
2333 if self.attrs:
2330 if self.attrs:
2334 self.displayattrs = self.attrs
2331 self.displayattrs = self.attrs
2335 else:
2332 else:
2336 self.displayattrs = []
2333 self.displayattrs = []
2337 endy = min(self.datastarty+self.mainsizey, len(self.items))
2334 endy = min(self.datastarty+self.mainsizey, len(self.items))
2338 for i in xrange(self.datastarty, endy):
2335 for i in xrange(self.datastarty, endy):
2339 for attrname in xattrs(self.items[i].item, "default"):
2336 for attrname in xattrs(self.items[i].item, "default"):
2340 if attrname not in attrnames:
2337 if attrname not in attrnames:
2341 self.displayattrs.append(attrname)
2338 self.displayattrs.append(attrname)
2342 attrnames.add(attrname)
2339 attrnames.add(attrname)
2343
2340
2344 def getrow(self, i):
2341 def getrow(self, i):
2345 # Return a dictinary with the attributes for the object
2342 # Return a dictinary with the attributes for the object
2346 # ``self.items[i]``. Attribute names are taken from
2343 # ``self.items[i]``. Attribute names are taken from
2347 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
2344 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
2348 # called before.
2345 # called before.
2349 row = {}
2346 row = {}
2350 item = self.items[i].item
2347 item = self.items[i].item
2351 for attrname in self.displayattrs:
2348 for attrname in self.displayattrs:
2352 try:
2349 try:
2353 value = _getattr(item, attrname, _default)
2350 value = _getattr(item, attrname, _default)
2354 except (KeyboardInterrupt, SystemExit):
2351 except (KeyboardInterrupt, SystemExit):
2355 raise
2352 raise
2356 except Exception, exc:
2353 except Exception, exc:
2357 value = exc
2354 value = exc
2358 # only store attribute if it exists (or we got an exception)
2355 # only store attribute if it exists (or we got an exception)
2359 if value is not _default:
2356 if value is not _default:
2360 parts = []
2357 parts = []
2361 totallength = 0
2358 totallength = 0
2362 align = None
2359 align = None
2363 full = False
2360 full = False
2364 # Collect parts until we have enough
2361 # Collect parts until we have enough
2365 for part in xrepr(value, "cell"):
2362 for part in xrepr(value, "cell"):
2366 # part gives (alignment, stop)
2363 # part gives (alignment, stop)
2367 # instead of (style, text)
2364 # instead of (style, text)
2368 if isinstance(part[0], int):
2365 if isinstance(part[0], int):
2369 # only consider the first occurence
2366 # only consider the first occurence
2370 if align is None:
2367 if align is None:
2371 align = part[0]
2368 align = part[0]
2372 full = part[1]
2369 full = part[1]
2373 else:
2370 else:
2374 parts.append(part)
2371 parts.append(part)
2375 totallength += len(part[1])
2372 totallength += len(part[1])
2376 if totallength >= self.browser.maxattrlength and not full:
2373 if totallength >= self.browser.maxattrlength and not full:
2377 parts.append((style_ellisis, "..."))
2374 parts.append((style_ellisis, "..."))
2378 totallength += 3
2375 totallength += 3
2379 break
2376 break
2380 # remember alignment, length and colored parts
2377 # remember alignment, length and colored parts
2381 row[attrname] = (align, totallength, parts)
2378 row[attrname] = (align, totallength, parts)
2382 return row
2379 return row
2383
2380
2384 def calcwidths(self):
2381 def calcwidths(self):
2385 # Recalculate the displayed fields and their width.
2382 # Recalculate the displayed fields and their width.
2386 # ``calcdisplayattrs()'' must have been called and the cache
2383 # ``calcdisplayattrs()'' must have been called and the cache
2387 # for attributes of the objects on screen (``self.displayrows``)
2384 # for attributes of the objects on screen (``self.displayrows``)
2388 # must have been filled. This returns a dictionary mapping
2385 # must have been filled. This returns a dictionary mapping
2389 # colmn names to width.
2386 # colmn names to width.
2390 self.colwidths = {}
2387 self.colwidths = {}
2391 for row in self.displayrows:
2388 for row in self.displayrows:
2392 for attrname in self.displayattrs:
2389 for attrname in self.displayattrs:
2393 try:
2390 try:
2394 length = row[attrname][1]
2391 length = row[attrname][1]
2395 except KeyError:
2392 except KeyError:
2396 length = 0
2393 length = 0
2397 # always add attribute to colwidths, even if it doesn't exist
2394 # always add attribute to colwidths, even if it doesn't exist
2398 if attrname not in self.colwidths:
2395 if attrname not in self.colwidths:
2399 self.colwidths[attrname] = len(_attrname(attrname))
2396 self.colwidths[attrname] = len(_attrname(attrname))
2400 newwidth = max(self.colwidths[attrname], length)
2397 newwidth = max(self.colwidths[attrname], length)
2401 self.colwidths[attrname] = newwidth
2398 self.colwidths[attrname] = newwidth
2402
2399
2403 # How many characters do we need to paint the item number?
2400 # How many characters do we need to paint the item number?
2404 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
2401 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
2405 # How must space have we got to display data?
2402 # How must space have we got to display data?
2406 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
2403 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
2407 # width of all columns
2404 # width of all columns
2408 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
2405 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
2409
2406
2410 def calcdisplayattr(self):
2407 def calcdisplayattr(self):
2411 # Find out on which attribute the cursor is on and store this
2408 # Find out on which attribute the cursor is on and store this
2412 # information in ``self.displayattr``.
2409 # information in ``self.displayattr``.
2413 pos = 0
2410 pos = 0
2414 for (i, attrname) in enumerate(self.displayattrs):
2411 for (i, attrname) in enumerate(self.displayattrs):
2415 if pos+self.colwidths[attrname] >= self.curx:
2412 if pos+self.colwidths[attrname] >= self.curx:
2416 self.displayattr = (i, attrname)
2413 self.displayattr = (i, attrname)
2417 break
2414 break
2418 pos += self.colwidths[attrname]+1
2415 pos += self.colwidths[attrname]+1
2419 else:
2416 else:
2420 self.displayattr = (None, _default)
2417 self.displayattr = (None, _default)
2421
2418
2422 def moveto(self, x, y, refresh=False):
2419 def moveto(self, x, y, refresh=False):
2423 # Move the cursor to the position ``(x,y)`` (in data coordinates,
2420 # Move the cursor to the position ``(x,y)`` (in data coordinates,
2424 # not in screen coordinates). If ``refresh`` is true, all cached
2421 # not in screen coordinates). If ``refresh`` is true, all cached
2425 # values will be recalculated (e.g. because the list has been
2422 # values will be recalculated (e.g. because the list has been
2426 # resorted, so screen positions etc. are no longer valid).
2423 # resorted, so screen positions etc. are no longer valid).
2427 olddatastarty = self.datastarty
2424 olddatastarty = self.datastarty
2428 oldx = self.curx
2425 oldx = self.curx
2429 oldy = self.cury
2426 oldy = self.cury
2430 x = int(x+0.5)
2427 x = int(x+0.5)
2431 y = int(y+0.5)
2428 y = int(y+0.5)
2432 newx = x # remember where we wanted to move
2429 newx = x # remember where we wanted to move
2433 newy = y # remember where we wanted to move
2430 newy = y # remember where we wanted to move
2434
2431
2435 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
2432 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
2436 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
2433 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
2437
2434
2438 # Make sure that the cursor didn't leave the main area vertically
2435 # Make sure that the cursor didn't leave the main area vertically
2439 if y < 0:
2436 if y < 0:
2440 y = 0
2437 y = 0
2441 self.fetch(y+scrollbordery+1) # try to get more items
2438 self.fetch(y+scrollbordery+1) # try to get more items
2442 if y >= len(self.items):
2439 if y >= len(self.items):
2443 y = max(0, len(self.items)-1)
2440 y = max(0, len(self.items)-1)
2444
2441
2445 # Make sure that the cursor stays on screen vertically
2442 # Make sure that the cursor stays on screen vertically
2446 if y < self.datastarty+scrollbordery:
2443 if y < self.datastarty+scrollbordery:
2447 self.datastarty = max(0, y-scrollbordery)
2444 self.datastarty = max(0, y-scrollbordery)
2448 elif y >= self.datastarty+self.mainsizey-scrollbordery:
2445 elif y >= self.datastarty+self.mainsizey-scrollbordery:
2449 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
2446 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
2450 len(self.items)-self.mainsizey))
2447 len(self.items)-self.mainsizey))
2451
2448
2452 if refresh: # Do we need to refresh the complete display?
2449 if refresh: # Do we need to refresh the complete display?
2453 self.calcdisplayattrs()
2450 self.calcdisplayattrs()
2454 endy = min(self.datastarty+self.mainsizey, len(self.items))
2451 endy = min(self.datastarty+self.mainsizey, len(self.items))
2455 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
2452 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
2456 self.calcwidths()
2453 self.calcwidths()
2457 # Did we scroll vertically => update displayrows
2454 # Did we scroll vertically => update displayrows
2458 # and various other attributes
2455 # and various other attributes
2459 elif self.datastarty != olddatastarty:
2456 elif self.datastarty != olddatastarty:
2460 # Recalculate which attributes we have to display
2457 # Recalculate which attributes we have to display
2461 olddisplayattrs = self.displayattrs
2458 olddisplayattrs = self.displayattrs
2462 self.calcdisplayattrs()
2459 self.calcdisplayattrs()
2463 # If there are new attributes, recreate the cache
2460 # If there are new attributes, recreate the cache
2464 if self.displayattrs != olddisplayattrs:
2461 if self.displayattrs != olddisplayattrs:
2465 endy = min(self.datastarty+self.mainsizey, len(self.items))
2462 endy = min(self.datastarty+self.mainsizey, len(self.items))
2466 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
2463 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
2467 elif self.datastarty<olddatastarty: # we did scroll up
2464 elif self.datastarty<olddatastarty: # we did scroll up
2468 # drop rows from the end
2465 # drop rows from the end
2469 del self.displayrows[self.datastarty-olddatastarty:]
2466 del self.displayrows[self.datastarty-olddatastarty:]
2470 # fetch new items
2467 # fetch new items
2471 for i in xrange(olddatastarty-1,
2468 for i in xrange(olddatastarty-1,
2472 self.datastarty-1, -1):
2469 self.datastarty-1, -1):
2473 try:
2470 try:
2474 row = self.getrow(i)
2471 row = self.getrow(i)
2475 except IndexError:
2472 except IndexError:
2476 # we didn't have enough objects to fill the screen
2473 # we didn't have enough objects to fill the screen
2477 break
2474 break
2478 self.displayrows.insert(0, row)
2475 self.displayrows.insert(0, row)
2479 else: # we did scroll down
2476 else: # we did scroll down
2480 # drop rows from the start
2477 # drop rows from the start
2481 del self.displayrows[:self.datastarty-olddatastarty]
2478 del self.displayrows[:self.datastarty-olddatastarty]
2482 # fetch new items
2479 # fetch new items
2483 for i in xrange(olddatastarty+self.mainsizey,
2480 for i in xrange(olddatastarty+self.mainsizey,
2484 self.datastarty+self.mainsizey):
2481 self.datastarty+self.mainsizey):
2485 try:
2482 try:
2486 row = self.getrow(i)
2483 row = self.getrow(i)
2487 except IndexError:
2484 except IndexError:
2488 # we didn't have enough objects to fill the screen
2485 # we didn't have enough objects to fill the screen
2489 break
2486 break
2490 self.displayrows.append(row)
2487 self.displayrows.append(row)
2491 self.calcwidths()
2488 self.calcwidths()
2492
2489
2493 # Make sure that the cursor didn't leave the data area horizontally
2490 # Make sure that the cursor didn't leave the data area horizontally
2494 if x < 0:
2491 if x < 0:
2495 x = 0
2492 x = 0
2496 elif x >= self.datasizex:
2493 elif x >= self.datasizex:
2497 x = max(0, self.datasizex-1)
2494 x = max(0, self.datasizex-1)
2498
2495
2499 # Make sure that the cursor stays on screen horizontally
2496 # Make sure that the cursor stays on screen horizontally
2500 if x < self.datastartx+scrollborderx:
2497 if x < self.datastartx+scrollborderx:
2501 self.datastartx = max(0, x-scrollborderx)
2498 self.datastartx = max(0, x-scrollborderx)
2502 elif x >= self.datastartx+self.mainsizex-scrollborderx:
2499 elif x >= self.datastartx+self.mainsizex-scrollborderx:
2503 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
2500 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
2504 self.datasizex-self.mainsizex))
2501 self.datasizex-self.mainsizex))
2505
2502
2506 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
2503 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
2507 self.browser.beep()
2504 self.browser.beep()
2508 else:
2505 else:
2509 self.curx = x
2506 self.curx = x
2510 self.cury = y
2507 self.cury = y
2511 self.calcdisplayattr()
2508 self.calcdisplayattr()
2512
2509
2513 def sort(self, key, reverse=False):
2510 def sort(self, key, reverse=False):
2514 """
2511 """
2515 Sort the currently list of items using the key function ``key``. If
2512 Sort the currently list of items using the key function ``key``. If
2516 ``reverse`` is true the sort order is reversed.
2513 ``reverse`` is true the sort order is reversed.
2517 """
2514 """
2518 curitem = self.items[self.cury] # Remember where the cursor is now
2515 curitem = self.items[self.cury] # Remember where the cursor is now
2519
2516
2520 # Sort items
2517 # Sort items
2521 def realkey(item):
2518 def realkey(item):
2522 return key(item.item)
2519 return key(item.item)
2523 self.items = deque(sorted(self.items, key=realkey, reverse=reverse))
2520 self.items = deque(sorted(self.items, key=realkey, reverse=reverse))
2524
2521
2525 # Find out where the object under the cursor went
2522 # Find out where the object under the cursor went
2526 cury = self.cury
2523 cury = self.cury
2527 for (i, item) in enumerate(self.items):
2524 for (i, item) in enumerate(self.items):
2528 if item is curitem:
2525 if item is curitem:
2529 cury = i
2526 cury = i
2530 break
2527 break
2531
2528
2532 self.moveto(self.curx, cury, refresh=True)
2529 self.moveto(self.curx, cury, refresh=True)
2533
2530
2534
2531
2535 class ibrowse(Display):
2532 class ibrowse(Display):
2536 # Show this many lines from the previous screen when paging horizontally
2533 # Show this many lines from the previous screen when paging horizontally
2537 pageoverlapx = 1
2534 pageoverlapx = 1
2538
2535
2539 # Show this many lines from the previous screen when paging vertically
2536 # Show this many lines from the previous screen when paging vertically
2540 pageoverlapy = 1
2537 pageoverlapy = 1
2541
2538
2542 # Start scrolling when the cursor is less than this number of columns
2539 # Start scrolling when the cursor is less than this number of columns
2543 # away from the left or right screen edge
2540 # away from the left or right screen edge
2544 scrollborderx = 10
2541 scrollborderx = 10
2545
2542
2546 # Start scrolling when the cursor is less than this number of lines
2543 # Start scrolling when the cursor is less than this number of lines
2547 # away from the top or bottom screen edge
2544 # away from the top or bottom screen edge
2548 scrollbordery = 5
2545 scrollbordery = 5
2549
2546
2550 # Accelerate by this factor when scrolling horizontally
2547 # Accelerate by this factor when scrolling horizontally
2551 acceleratex = 1.05
2548 acceleratex = 1.05
2552
2549
2553 # Accelerate by this factor when scrolling vertically
2550 # Accelerate by this factor when scrolling vertically
2554 acceleratey = 1.05
2551 acceleratey = 1.05
2555
2552
2556 # The maximum horizontal scroll speed
2553 # The maximum horizontal scroll speed
2557 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
2554 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
2558 maxspeedx = 0.5
2555 maxspeedx = 0.5
2559
2556
2560 # The maximum vertical scroll speed
2557 # The maximum vertical scroll speed
2561 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
2558 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
2562 maxspeedy = 0.5
2559 maxspeedy = 0.5
2563
2560
2564 # The maximum number of header lines for browser level
2561 # The maximum number of header lines for browser level
2565 # if the nesting is deeper, only the innermost levels are displayed
2562 # if the nesting is deeper, only the innermost levels are displayed
2566 maxheaders = 5
2563 maxheaders = 5
2567
2564
2568 # The approximate maximum length of a column entry
2565 # The approximate maximum length of a column entry
2569 maxattrlength = 200
2566 maxattrlength = 200
2570
2567
2571 # Styles for various parts of the GUI
2568 # Styles for various parts of the GUI
2572 style_objheadertext = Style(COLOR_WHITE, COLOR_BLACK, A_BOLD|A_REVERSE)
2569 style_objheadertext = Style(COLOR_WHITE, COLOR_BLACK, A_BOLD|A_REVERSE)
2573 style_objheadernumber = Style(COLOR_WHITE, COLOR_BLUE, A_BOLD|A_REVERSE)
2570 style_objheadernumber = Style(COLOR_WHITE, COLOR_BLUE, A_BOLD|A_REVERSE)
2574 style_objheaderobject = Style(COLOR_WHITE, COLOR_BLACK, A_REVERSE)
2571 style_objheaderobject = Style(COLOR_WHITE, COLOR_BLACK, A_REVERSE)
2575 style_colheader = Style(COLOR_BLUE, COLOR_WHITE, A_REVERSE)
2572 style_colheader = Style(COLOR_BLUE, COLOR_WHITE, A_REVERSE)
2576 style_colheaderhere = Style(COLOR_GREEN, COLOR_BLACK, A_BOLD|A_REVERSE)
2573 style_colheaderhere = Style(COLOR_GREEN, COLOR_BLACK, A_BOLD|A_REVERSE)
2577 style_colheadersep = Style(COLOR_BLUE, COLOR_BLACK, A_REVERSE)
2574 style_colheadersep = Style(COLOR_BLUE, COLOR_BLACK, A_REVERSE)
2578 style_number = Style(COLOR_BLUE, COLOR_WHITE, A_REVERSE)
2575 style_number = Style(COLOR_BLUE, COLOR_WHITE, A_REVERSE)
2579 style_numberhere = Style(COLOR_GREEN, COLOR_BLACK, A_BOLD|A_REVERSE)
2576 style_numberhere = Style(COLOR_GREEN, COLOR_BLACK, A_BOLD|A_REVERSE)
2580 style_sep = Style(COLOR_BLUE, COLOR_BLACK)
2577 style_sep = Style(COLOR_BLUE, COLOR_BLACK)
2581 style_data = Style(COLOR_WHITE, COLOR_BLACK)
2578 style_data = Style(COLOR_WHITE, COLOR_BLACK)
2582 style_datapad = Style(COLOR_BLUE, COLOR_BLACK, A_BOLD)
2579 style_datapad = Style(COLOR_BLUE, COLOR_BLACK, A_BOLD)
2583 style_footer = Style(COLOR_BLACK, COLOR_WHITE)
2580 style_footer = Style(COLOR_BLACK, COLOR_WHITE)
2584 style_report = Style(COLOR_WHITE, COLOR_BLACK)
2581 style_report = Style(COLOR_WHITE, COLOR_BLACK)
2585
2582
2586 # Column separator in header
2583 # Column separator in header
2587 headersepchar = "|"
2584 headersepchar = "|"
2588
2585
2589 # Character for padding data cell entries
2586 # Character for padding data cell entries
2590 datapadchar = "."
2587 datapadchar = "."
2591
2588
2592 # Column separator in data area
2589 # Column separator in data area
2593 datasepchar = "|"
2590 datasepchar = "|"
2594
2591
2595 # Character to use for "empty" cell (i.e. for non-existing attributes)
2592 # Character to use for "empty" cell (i.e. for non-existing attributes)
2596 nodatachar = "-"
2593 nodatachar = "-"
2597
2594
2598 # Prompts for modes that require keyboard input
2595 # Prompts for modes that require keyboard input
2599 prompts = {
2596 prompts = {
2600 "goto": "goto object #: ",
2597 "goto": "goto object #: ",
2601 "find": "find expression: ",
2598 "find": "find expression: ",
2602 "findbackwards": "find backwards expression: "
2599 "findbackwards": "find backwards expression: "
2603 }
2600 }
2604
2601
2605 # Maps curses key codes to "function" names
2602 # Maps curses key codes to "function" names
2606 keymap = {
2603 keymap = {
2607 ord("q"): "quit",
2604 ord("q"): "quit",
2608 curses.KEY_UP: "up",
2605 curses.KEY_UP: "up",
2609 curses.KEY_DOWN: "down",
2606 curses.KEY_DOWN: "down",
2610 curses.KEY_PPAGE: "pageup",
2607 curses.KEY_PPAGE: "pageup",
2611 curses.KEY_NPAGE: "pagedown",
2608 curses.KEY_NPAGE: "pagedown",
2612 curses.KEY_LEFT: "left",
2609 curses.KEY_LEFT: "left",
2613 curses.KEY_RIGHT: "right",
2610 curses.KEY_RIGHT: "right",
2614 curses.KEY_HOME: "home",
2611 curses.KEY_HOME: "home",
2615 curses.KEY_END: "end",
2612 curses.KEY_END: "end",
2616 ord("<"): "prevattr",
2613 ord("<"): "prevattr",
2617 0x1b: "prevattr", # SHIFT-TAB
2614 0x1b: "prevattr", # SHIFT-TAB
2618 ord(">"): "nextattr",
2615 ord(">"): "nextattr",
2619 ord("\t"):"nextattr", # TAB
2616 ord("\t"):"nextattr", # TAB
2620 ord("p"): "pick",
2617 ord("p"): "pick",
2621 ord("P"): "pickattr",
2618 ord("P"): "pickattr",
2622 ord("C"): "pickallattrs",
2619 ord("C"): "pickallattrs",
2623 ord("m"): "pickmarked",
2620 ord("m"): "pickmarked",
2624 ord("M"): "pickmarkedattr",
2621 ord("M"): "pickmarkedattr",
2625 ord("\n"): "enterdefault",
2622 ord("\n"): "enterdefault",
2626 # FIXME: What's happening here?
2623 # FIXME: What's happening here?
2627 8: "leave",
2624 8: "leave",
2628 127: "leave",
2625 127: "leave",
2629 curses.KEY_BACKSPACE: "leave",
2626 curses.KEY_BACKSPACE: "leave",
2630 ord("x"): "leave",
2627 ord("x"): "leave",
2631 ord("h"): "help",
2628 ord("h"): "help",
2632 ord("e"): "enter",
2629 ord("e"): "enter",
2633 ord("E"): "enterattr",
2630 ord("E"): "enterattr",
2634 ord("d"): "detail",
2631 ord("d"): "detail",
2635 ord("D"): "detailattr",
2632 ord("D"): "detailattr",
2636 ord(" "): "tooglemark",
2633 ord(" "): "tooglemark",
2637 ord("r"): "markrange",
2634 ord("r"): "markrange",
2638 ord("v"): "sortattrasc",
2635 ord("v"): "sortattrasc",
2639 ord("V"): "sortattrdesc",
2636 ord("V"): "sortattrdesc",
2640 ord("g"): "goto",
2637 ord("g"): "goto",
2641 ord("f"): "find",
2638 ord("f"): "find",
2642 ord("b"): "findbackwards",
2639 ord("b"): "findbackwards",
2643 }
2640 }
2644
2641
2645 def __init__(self, *attrs):
2642 def __init__(self, *attrs):
2646 """
2643 """
2647 Create a new browser. If ``attrs`` is not empty, it is the list
2644 Create a new browser. If ``attrs`` is not empty, it is the list
2648 of attributes that will be displayed in the browser, otherwise
2645 of attributes that will be displayed in the browser, otherwise
2649 these will be determined by the objects on screen.
2646 these will be determined by the objects on screen.
2650 """
2647 """
2651 self.attrs = attrs
2648 self.attrs = attrs
2652
2649
2653 # Stack of browser levels
2650 # Stack of browser levels
2654 self.levels = []
2651 self.levels = []
2655 # how many colums to scroll (Changes when accelerating)
2652 # how many colums to scroll (Changes when accelerating)
2656 self.stepx = 1.
2653 self.stepx = 1.
2657
2654
2658 # how many rows to scroll (Changes when accelerating)
2655 # how many rows to scroll (Changes when accelerating)
2659 self.stepy = 1.
2656 self.stepy = 1.
2660
2657
2661 # Beep on the edges of the data area? (Will be set to ``False``
2658 # Beep on the edges of the data area? (Will be set to ``False``
2662 # once the cursor hits the edge of the screen, so we don't get
2659 # once the cursor hits the edge of the screen, so we don't get
2663 # multiple beeps).
2660 # multiple beeps).
2664 self._dobeep = True
2661 self._dobeep = True
2665
2662
2666 # Cache for registered ``curses`` colors and styles.
2663 # Cache for registered ``curses`` colors and styles.
2667 self._styles = {}
2664 self._styles = {}
2668 self._colors = {}
2665 self._colors = {}
2669 self._maxcolor = 1
2666 self._maxcolor = 1
2670
2667
2671 # How many header lines do we want to paint (the numbers of levels
2668 # How many header lines do we want to paint (the numbers of levels
2672 # we have, but with an upper bound)
2669 # we have, but with an upper bound)
2673 self._headerlines = 1
2670 self._headerlines = 1
2674
2671
2675 # Index of first header line
2672 # Index of first header line
2676 self._firstheaderline = 0
2673 self._firstheaderline = 0
2677
2674
2678 # curses window
2675 # curses window
2679 self.scr = None
2676 self.scr = None
2680 # report in the footer line (error, executed command etc.)
2677 # report in the footer line (error, executed command etc.)
2681 self._report = None
2678 self._report = None
2682
2679
2683 # value to be returned to the caller (set by commands)
2680 # value to be returned to the caller (set by commands)
2684 self.returnvalue = None
2681 self.returnvalue = None
2685
2682
2686 # The mode the browser is in
2683 # The mode the browser is in
2687 # e.g. normal browsing or entering an argument for a command
2684 # e.g. normal browsing or entering an argument for a command
2688 self.mode = "default"
2685 self.mode = "default"
2689
2686
2690 # The partially entered row number for the goto command
2687 # The partially entered row number for the goto command
2691 self.goto = ""
2688 self.goto = ""
2692
2689
2693 def nextstepx(self, step):
2690 def nextstepx(self, step):
2694 """
2691 """
2695 Accelerate horizontally.
2692 Accelerate horizontally.
2696 """
2693 """
2697 return max(1., min(step*self.acceleratex,
2694 return max(1., min(step*self.acceleratex,
2698 self.maxspeedx*self.levels[-1].mainsizex))
2695 self.maxspeedx*self.levels[-1].mainsizex))
2699
2696
2700 def nextstepy(self, step):
2697 def nextstepy(self, step):
2701 """
2698 """
2702 Accelerate vertically.
2699 Accelerate vertically.
2703 """
2700 """
2704 return max(1., min(step*self.acceleratey,
2701 return max(1., min(step*self.acceleratey,
2705 self.maxspeedy*self.levels[-1].mainsizey))
2702 self.maxspeedy*self.levels[-1].mainsizey))
2706
2703
2707 def getstyle(self, style):
2704 def getstyle(self, style):
2708 """
2705 """
2709 Register the ``style`` with ``curses`` or get it from the cache,
2706 Register the ``style`` with ``curses`` or get it from the cache,
2710 if it has been registered before.
2707 if it has been registered before.
2711 """
2708 """
2712 try:
2709 try:
2713 return self._styles[style.fg, style.bg, style.attrs]
2710 return self._styles[style.fg, style.bg, style.attrs]
2714 except KeyError:
2711 except KeyError:
2715 attrs = 0
2712 attrs = 0
2716 for b in A2CURSES:
2713 for b in A2CURSES:
2717 if style.attrs & b:
2714 if style.attrs & b:
2718 attrs |= A2CURSES[b]
2715 attrs |= A2CURSES[b]
2719 try:
2716 try:
2720 color = self._colors[style.fg, style.bg]
2717 color = self._colors[style.fg, style.bg]
2721 except KeyError:
2718 except KeyError:
2722 curses.init_pair(
2719 curses.init_pair(
2723 self._maxcolor,
2720 self._maxcolor,
2724 COLOR2CURSES[style.fg],
2721 COLOR2CURSES[style.fg],
2725 COLOR2CURSES[style.bg]
2722 COLOR2CURSES[style.bg]
2726 )
2723 )
2727 color = curses.color_pair(self._maxcolor)
2724 color = curses.color_pair(self._maxcolor)
2728 self._colors[style.fg, style.bg] = color
2725 self._colors[style.fg, style.bg] = color
2729 self._maxcolor += 1
2726 self._maxcolor += 1
2730 c = color | attrs
2727 c = color | attrs
2731 self._styles[style.fg, style.bg, style.attrs] = c
2728 self._styles[style.fg, style.bg, style.attrs] = c
2732 return c
2729 return c
2733
2730
2734 def addstr(self, y, x, begx, endx, text, style):
2731 def addstr(self, y, x, begx, endx, text, style):
2735 """
2732 """
2736 A version of ``curses.addstr()`` that can handle ``x`` coordinates
2733 A version of ``curses.addstr()`` that can handle ``x`` coordinates
2737 that are outside the screen.
2734 that are outside the screen.
2738 """
2735 """
2739 text2 = text[max(0, begx-x):max(0, endx-x)]
2736 text2 = text[max(0, begx-x):max(0, endx-x)]
2740 if text2:
2737 if text2:
2741 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
2738 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
2742 return len(text)
2739 return len(text)
2743
2740
2744 def addchr(self, y, x, begx, endx, c, l, style):
2741 def addchr(self, y, x, begx, endx, c, l, style):
2745 x0 = max(x, begx)
2742 x0 = max(x, begx)
2746 x1 = min(x+l, endx)
2743 x1 = min(x+l, endx)
2747 if x1>x0:
2744 if x1>x0:
2748 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
2745 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
2749 return l
2746 return l
2750
2747
2751 def _calcheaderlines(self, levels):
2748 def _calcheaderlines(self, levels):
2752 # Calculate how many headerlines do we have to display, if we have
2749 # Calculate how many headerlines do we have to display, if we have
2753 # ``levels`` browser levels
2750 # ``levels`` browser levels
2754 if levels is None:
2751 if levels is None:
2755 levels = len(self.levels)
2752 levels = len(self.levels)
2756 self._headerlines = min(self.maxheaders, levels)
2753 self._headerlines = min(self.maxheaders, levels)
2757 self._firstheaderline = levels-self._headerlines
2754 self._firstheaderline = levels-self._headerlines
2758
2755
2759 def getstylehere(self, style):
2756 def getstylehere(self, style):
2760 """
2757 """
2761 Return a style for displaying the original style ``style``
2758 Return a style for displaying the original style ``style``
2762 in the row the cursor is on.
2759 in the row the cursor is on.
2763 """
2760 """
2764 return Style(style.fg, style.bg, style.attrs | A_BOLD)
2761 return Style(style.fg, style.bg, style.attrs | A_BOLD)
2765
2762
2766 def report(self, msg):
2763 def report(self, msg):
2767 """
2764 """
2768 Store the message ``msg`` for display below the footer line. This
2765 Store the message ``msg`` for display below the footer line. This
2769 will be displayed as soon as the screen is redrawn.
2766 will be displayed as soon as the screen is redrawn.
2770 """
2767 """
2771 self._report = msg
2768 self._report = msg
2772
2769
2773 def enter(self, item, mode, *attrs):
2770 def enter(self, item, mode, *attrs):
2774 """
2771 """
2775 Enter the object ``item`` in the mode ``mode``. If ``attrs`` is
2772 Enter the object ``item`` in the mode ``mode``. If ``attrs`` is
2776 specified, it will be used as a fixed list of attributes to display.
2773 specified, it will be used as a fixed list of attributes to display.
2777 """
2774 """
2778 try:
2775 try:
2779 iterator = xiter(item, mode)
2776 iterator = xiter(item, mode)
2780 except (KeyboardInterrupt, SystemExit):
2777 except (KeyboardInterrupt, SystemExit):
2781 raise
2778 raise
2782 except Exception, exc:
2779 except Exception, exc:
2783 curses.beep()
2780 curses.beep()
2784 self.report(exc)
2781 self.report(exc)
2785 else:
2782 else:
2786 self._calcheaderlines(len(self.levels)+1)
2783 self._calcheaderlines(len(self.levels)+1)
2787 level = _BrowserLevel(
2784 level = _BrowserLevel(
2788 self,
2785 self,
2789 item,
2786 item,
2790 iterator,
2787 iterator,
2791 self.scrsizey-1-self._headerlines-2,
2788 self.scrsizey-1-self._headerlines-2,
2792 *attrs
2789 *attrs
2793 )
2790 )
2794 self.levels.append(level)
2791 self.levels.append(level)
2795
2792
2796 def startkeyboardinput(self, mode):
2793 def startkeyboardinput(self, mode):
2797 """
2794 """
2798 Enter mode ``mode``, which requires keyboard input.
2795 Enter mode ``mode``, which requires keyboard input.
2799 """
2796 """
2800 self.mode = mode
2797 self.mode = mode
2801 self.keyboardinput = ""
2798 self.keyboardinput = ""
2802 self.cursorpos = 0
2799 self.cursorpos = 0
2803
2800
2804 def executekeyboardinput(self, mode):
2801 def executekeyboardinput(self, mode):
2805 exe = getattr(self, "exe_%s" % mode, None)
2802 exe = getattr(self, "exe_%s" % mode, None)
2806 if exe is not None:
2803 if exe is not None:
2807 exe()
2804 exe()
2808 self.mode = "default"
2805 self.mode = "default"
2809
2806
2810 def keylabel(self, keycode):
2807 def keylabel(self, keycode):
2811 """
2808 """
2812 Return a pretty name for the ``curses`` key ``keycode`` (used in the
2809 Return a pretty name for the ``curses`` key ``keycode`` (used in the
2813 help screen and in reports about unassigned keys).
2810 help screen and in reports about unassigned keys).
2814 """
2811 """
2815 if keycode <= 0xff:
2812 if keycode <= 0xff:
2816 specialsnames = {
2813 specialsnames = {
2817 ord("\n"): "RETURN",
2814 ord("\n"): "RETURN",
2818 ord(" "): "SPACE",
2815 ord(" "): "SPACE",
2819 ord("\t"): "TAB",
2816 ord("\t"): "TAB",
2820 ord("\x7f"): "DELETE",
2817 ord("\x7f"): "DELETE",
2821 ord("\x08"): "BACKSPACE",
2818 ord("\x08"): "BACKSPACE",
2822 }
2819 }
2823 if keycode in specialsnames:
2820 if keycode in specialsnames:
2824 return specialsnames[keycode]
2821 return specialsnames[keycode]
2825 return repr(chr(keycode))
2822 return repr(chr(keycode))
2826 for name in dir(curses):
2823 for name in dir(curses):
2827 if name.startswith("KEY_") and getattr(curses, name) == keycode:
2824 if name.startswith("KEY_") and getattr(curses, name) == keycode:
2828 return name
2825 return name
2829 return str(keycode)
2826 return str(keycode)
2830
2827
2831 def beep(self, force=False):
2828 def beep(self, force=False):
2832 if force or self._dobeep:
2829 if force or self._dobeep:
2833 curses.beep()
2830 curses.beep()
2834 # don't beep again (as long as the same key is pressed)
2831 # don't beep again (as long as the same key is pressed)
2835 self._dobeep = False
2832 self._dobeep = False
2836
2833
2837 def cmd_quit(self):
2834 def cmd_quit(self):
2838 self.returnvalue = None
2835 self.returnvalue = None
2839 return True
2836 return True
2840
2837
2841 def cmd_up(self):
2838 def cmd_up(self):
2842 level = self.levels[-1]
2839 level = self.levels[-1]
2843 self.report("up")
2840 self.report("up")
2844 level.moveto(level.curx, level.cury-self.stepy)
2841 level.moveto(level.curx, level.cury-self.stepy)
2845
2842
2846 def cmd_down(self):
2843 def cmd_down(self):
2847 level = self.levels[-1]
2844 level = self.levels[-1]
2848 self.report("down")
2845 self.report("down")
2849 level.moveto(level.curx, level.cury+self.stepy)
2846 level.moveto(level.curx, level.cury+self.stepy)
2850
2847
2851 def cmd_pageup(self):
2848 def cmd_pageup(self):
2852 level = self.levels[-1]
2849 level = self.levels[-1]
2853 self.report("page up")
2850 self.report("page up")
2854 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
2851 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
2855
2852
2856 def cmd_pagedown(self):
2853 def cmd_pagedown(self):
2857 level = self.levels[-1]
2854 level = self.levels[-1]
2858 self.report("page down")
2855 self.report("page down")
2859 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
2856 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
2860
2857
2861 def cmd_left(self):
2858 def cmd_left(self):
2862 level = self.levels[-1]
2859 level = self.levels[-1]
2863 self.report("left")
2860 self.report("left")
2864 level.moveto(level.curx-self.stepx, level.cury)
2861 level.moveto(level.curx-self.stepx, level.cury)
2865
2862
2866 def cmd_right(self):
2863 def cmd_right(self):
2867 level = self.levels[-1]
2864 level = self.levels[-1]
2868 self.report("right")
2865 self.report("right")
2869 level.moveto(level.curx+self.stepx, level.cury)
2866 level.moveto(level.curx+self.stepx, level.cury)
2870
2867
2871 def cmd_home(self):
2868 def cmd_home(self):
2872 level = self.levels[-1]
2869 level = self.levels[-1]
2873 self.report("home")
2870 self.report("home")
2874 level.moveto(0, level.cury)
2871 level.moveto(0, level.cury)
2875
2872
2876 def cmd_end(self):
2873 def cmd_end(self):
2877 level = self.levels[-1]
2874 level = self.levels[-1]
2878 self.report("end")
2875 self.report("end")
2879 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
2876 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
2880
2877
2881 def cmd_prevattr(self):
2878 def cmd_prevattr(self):
2882 level = self.levels[-1]
2879 level = self.levels[-1]
2883 if level.displayattr[0] is None or level.displayattr[0] == 0:
2880 if level.displayattr[0] is None or level.displayattr[0] == 0:
2884 self.beep()
2881 self.beep()
2885 else:
2882 else:
2886 self.report("prevattr")
2883 self.report("prevattr")
2887 pos = 0
2884 pos = 0
2888 for (i, attrname) in enumerate(level.displayattrs):
2885 for (i, attrname) in enumerate(level.displayattrs):
2889 if i == level.displayattr[0]-1:
2886 if i == level.displayattr[0]-1:
2890 break
2887 break
2891 pos += level.colwidths[attrname] + 1
2888 pos += level.colwidths[attrname] + 1
2892 level.moveto(pos, level.cury)
2889 level.moveto(pos, level.cury)
2893
2890
2894 def cmd_nextattr(self):
2891 def cmd_nextattr(self):
2895 level = self.levels[-1]
2892 level = self.levels[-1]
2896 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
2893 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
2897 self.beep()
2894 self.beep()
2898 else:
2895 else:
2899 self.report("nextattr")
2896 self.report("nextattr")
2900 pos = 0
2897 pos = 0
2901 for (i, attrname) in enumerate(level.displayattrs):
2898 for (i, attrname) in enumerate(level.displayattrs):
2902 if i == level.displayattr[0]+1:
2899 if i == level.displayattr[0]+1:
2903 break
2900 break
2904 pos += level.colwidths[attrname] + 1
2901 pos += level.colwidths[attrname] + 1
2905 level.moveto(pos, level.cury)
2902 level.moveto(pos, level.cury)
2906
2903
2907 def cmd_pick(self):
2904 def cmd_pick(self):
2908 level = self.levels[-1]
2905 level = self.levels[-1]
2909 self.returnvalue = level.items[level.cury].item
2906 self.returnvalue = level.items[level.cury].item
2910 return True
2907 return True
2911
2908
2912 def cmd_pickattr(self):
2909 def cmd_pickattr(self):
2913 level = self.levels[-1]
2910 level = self.levels[-1]
2914 attrname = level.displayattr[1]
2911 attrname = level.displayattr[1]
2915 if attrname is _default:
2912 if attrname is _default:
2916 curses.beep()
2913 curses.beep()
2917 self.report(AttributeError(_attrname(attrname)))
2914 self.report(AttributeError(_attrname(attrname)))
2918 return
2915 return
2919 attr = _getattr(level.items[level.cury].item, attrname)
2916 attr = _getattr(level.items[level.cury].item, attrname)
2920 if attr is _default:
2917 if attr is _default:
2921 curses.beep()
2918 curses.beep()
2922 self.report(AttributeError(_attrname(attrname)))
2919 self.report(AttributeError(_attrname(attrname)))
2923 else:
2920 else:
2924 self.returnvalue = attr
2921 self.returnvalue = attr
2925 return True
2922 return True
2926
2923
2927 def cmd_pickallattrs(self):
2924 def cmd_pickallattrs(self):
2928 level = self.levels[-1]
2925 level = self.levels[-1]
2929 attrname = level.displayattr[1]
2926 attrname = level.displayattr[1]
2930 if attrname is _default:
2927 if attrname is _default:
2931 curses.beep()
2928 curses.beep()
2932 self.report(AttributeError(_attrname(attrname)))
2929 self.report(AttributeError(_attrname(attrname)))
2933 return
2930 return
2934 result = []
2931 result = []
2935 for cache in level.items:
2932 for cache in level.items:
2936 attr = _getattr(cache.item, attrname)
2933 attr = _getattr(cache.item, attrname)
2937 if attr is not _default:
2934 if attr is not _default:
2938 result.append(attr)
2935 result.append(attr)
2939 self.returnvalue = result
2936 self.returnvalue = result
2940 return True
2937 return True
2941
2938
2942 def cmd_pickmarked(self):
2939 def cmd_pickmarked(self):
2943 level = self.levels[-1]
2940 level = self.levels[-1]
2944 self.returnvalue = [cache.item for cache in level.items if cache.marked]
2941 self.returnvalue = [cache.item for cache in level.items if cache.marked]
2945 return True
2942 return True
2946
2943
2947 def cmd_pickmarkedattr(self):
2944 def cmd_pickmarkedattr(self):
2948 level = self.levels[-1]
2945 level = self.levels[-1]
2949 attrname = level.displayattr[1]
2946 attrname = level.displayattr[1]
2950 if attrname is _default:
2947 if attrname is _default:
2951 curses.beep()
2948 curses.beep()
2952 self.report(AttributeError(_attrname(attrname)))
2949 self.report(AttributeError(_attrname(attrname)))
2953 return
2950 return
2954 result = []
2951 result = []
2955 for cache in level.items:
2952 for cache in level.items:
2956 if cache.marked:
2953 if cache.marked:
2957 attr = _getattr(cache.item, attrname)
2954 attr = _getattr(cache.item, attrname)
2958 if attr is not _default:
2955 if attr is not _default:
2959 result.append(attr)
2956 result.append(attr)
2960 self.returnvalue = result
2957 self.returnvalue = result
2961 return True
2958 return True
2962
2959
2963 def cmd_markrange(self):
2960 def cmd_markrange(self):
2964 level = self.levels[-1]
2961 level = self.levels[-1]
2965 self.report("markrange")
2962 self.report("markrange")
2966 start = None
2963 start = None
2967 if level.items:
2964 if level.items:
2968 for i in xrange(level.cury, -1, -1):
2965 for i in xrange(level.cury, -1, -1):
2969 if level.items[i].marked:
2966 if level.items[i].marked:
2970 start = i
2967 start = i
2971 break
2968 break
2972 if start is None:
2969 if start is None:
2973 self.report(CommandError("no mark before cursor"))
2970 self.report(CommandError("no mark before cursor"))
2974 curses.beep()
2971 curses.beep()
2975 else:
2972 else:
2976 for i in xrange(start, level.cury+1):
2973 for i in xrange(start, level.cury+1):
2977 cache = level.items[i]
2974 cache = level.items[i]
2978 if not cache.marked:
2975 if not cache.marked:
2979 cache.marked = True
2976 cache.marked = True
2980 level.marked += 1
2977 level.marked += 1
2981
2978
2982 def cmd_enterdefault(self):
2979 def cmd_enterdefault(self):
2983 level = self.levels[-1]
2980 level = self.levels[-1]
2984 try:
2981 try:
2985 item = level.items[level.cury].item
2982 item = level.items[level.cury].item
2986 except IndexError:
2983 except IndexError:
2987 self.report(CommandError("No object"))
2984 self.report(CommandError("No object"))
2988 curses.beep()
2985 curses.beep()
2989 else:
2986 else:
2990 self.report("entering object (default mode)...")
2987 self.report("entering object (default mode)...")
2991 self.enter(item, "default")
2988 self.enter(item, "default")
2992
2989
2993 def cmd_leave(self):
2990 def cmd_leave(self):
2994 self.report("leave")
2991 self.report("leave")
2995 if len(self.levels) > 1:
2992 if len(self.levels) > 1:
2996 self._calcheaderlines(len(self.levels)-1)
2993 self._calcheaderlines(len(self.levels)-1)
2997 self.levels.pop(-1)
2994 self.levels.pop(-1)
2998 else:
2995 else:
2999 self.report(CommandError("This is the last level"))
2996 self.report(CommandError("This is the last level"))
3000 curses.beep()
2997 curses.beep()
3001
2998
3002 def cmd_enter(self):
2999 def cmd_enter(self):
3003 level = self.levels[-1]
3000 level = self.levels[-1]
3004 try:
3001 try:
3005 item = level.items[level.cury].item
3002 item = level.items[level.cury].item
3006 except IndexError:
3003 except IndexError:
3007 self.report(CommandError("No object"))
3004 self.report(CommandError("No object"))
3008 curses.beep()
3005 curses.beep()
3009 else:
3006 else:
3010 self.report("entering object...")
3007 self.report("entering object...")
3011 self.enter(item, None)
3008 self.enter(item, None)
3012
3009
3013 def cmd_enterattr(self):
3010 def cmd_enterattr(self):
3014 level = self.levels[-1]
3011 level = self.levels[-1]
3015 attrname = level.displayattr[1]
3012 attrname = level.displayattr[1]
3016 if attrname is _default:
3013 if attrname is _default:
3017 curses.beep()
3014 curses.beep()
3018 self.report(AttributeError(_attrname(attrname)))
3015 self.report(AttributeError(_attrname(attrname)))
3019 return
3016 return
3020 try:
3017 try:
3021 item = level.items[level.cury].item
3018 item = level.items[level.cury].item
3022 except IndexError:
3019 except IndexError:
3023 self.report(CommandError("No object"))
3020 self.report(CommandError("No object"))
3024 curses.beep()
3021 curses.beep()
3025 else:
3022 else:
3026 attr = _getattr(item, attrname)
3023 attr = _getattr(item, attrname)
3027 if attr is _default:
3024 if attr is _default:
3028 self.report(AttributeError(_attrname(attrname)))
3025 self.report(AttributeError(_attrname(attrname)))
3029 else:
3026 else:
3030 self.report("entering object attribute %s..." % _attrname(attrname))
3027 self.report("entering object attribute %s..." % _attrname(attrname))
3031 self.enter(attr, None)
3028 self.enter(attr, None)
3032
3029
3033 def cmd_detail(self):
3030 def cmd_detail(self):
3034 level = self.levels[-1]
3031 level = self.levels[-1]
3035 try:
3032 try:
3036 item = level.items[level.cury].item
3033 item = level.items[level.cury].item
3037 except IndexError:
3034 except IndexError:
3038 self.report(CommandError("No object"))
3035 self.report(CommandError("No object"))
3039 curses.beep()
3036 curses.beep()
3040 else:
3037 else:
3041 self.report("entering detail view for object...")
3038 self.report("entering detail view for object...")
3042 self.enter(item, "detail")
3039 self.enter(item, "detail")
3043
3040
3044 def cmd_detailattr(self):
3041 def cmd_detailattr(self):
3045 level = self.levels[-1]
3042 level = self.levels[-1]
3046 attrname = level.displayattr[1]
3043 attrname = level.displayattr[1]
3047 if attrname is _default:
3044 if attrname is _default:
3048 curses.beep()
3045 curses.beep()
3049 self.report(AttributeError(_attrname(attrname)))
3046 self.report(AttributeError(_attrname(attrname)))
3050 return
3047 return
3051 try:
3048 try:
3052 item = level.items[level.cury].item
3049 item = level.items[level.cury].item
3053 except IndexError:
3050 except IndexError:
3054 self.report(CommandError("No object"))
3051 self.report(CommandError("No object"))
3055 curses.beep()
3052 curses.beep()
3056 else:
3053 else:
3057 attr = _getattr(item, attrname)
3054 attr = _getattr(item, attrname)
3058 if attr is _default:
3055 if attr is _default:
3059 self.report(AttributeError(_attrname(attrname)))
3056 self.report(AttributeError(_attrname(attrname)))
3060 else:
3057 else:
3061 self.report("entering detail view for attribute...")
3058 self.report("entering detail view for attribute...")
3062 self.enter(attr, "detail")
3059 self.enter(attr, "detail")
3063
3060
3064 def cmd_tooglemark(self):
3061 def cmd_tooglemark(self):
3065 level = self.levels[-1]
3062 level = self.levels[-1]
3066 self.report("toggle mark")
3063 self.report("toggle mark")
3067 try:
3064 try:
3068 item = level.items[level.cury]
3065 item = level.items[level.cury]
3069 except IndexError: # no items?
3066 except IndexError: # no items?
3070 pass
3067 pass
3071 else:
3068 else:
3072 if item.marked:
3069 if item.marked:
3073 item.marked = False
3070 item.marked = False
3074 level.marked -= 1
3071 level.marked -= 1
3075 else:
3072 else:
3076 item.marked = True
3073 item.marked = True
3077 level.marked += 1
3074 level.marked += 1
3078
3075
3079 def cmd_sortattrasc(self):
3076 def cmd_sortattrasc(self):
3080 level = self.levels[-1]
3077 level = self.levels[-1]
3081 attrname = level.displayattr[1]
3078 attrname = level.displayattr[1]
3082 if attrname is _default:
3079 if attrname is _default:
3083 curses.beep()
3080 curses.beep()
3084 self.report(AttributeError(_attrname(attrname)))
3081 self.report(AttributeError(_attrname(attrname)))
3085 return
3082 return
3086 self.report("sort by %s (ascending)" % _attrname(attrname))
3083 self.report("sort by %s (ascending)" % _attrname(attrname))
3087 def key(item):
3084 def key(item):
3088 try:
3085 try:
3089 return _getattr(item, attrname, None)
3086 return _getattr(item, attrname, None)
3090 except (KeyboardInterrupt, SystemExit):
3087 except (KeyboardInterrupt, SystemExit):
3091 raise
3088 raise
3092 except Exception:
3089 except Exception:
3093 return None
3090 return None
3094 level.sort(key)
3091 level.sort(key)
3095
3092
3096 def cmd_sortattrdesc(self):
3093 def cmd_sortattrdesc(self):
3097 level = self.levels[-1]
3094 level = self.levels[-1]
3098 attrname = level.displayattr[1]
3095 attrname = level.displayattr[1]
3099 if attrname is _default:
3096 if attrname is _default:
3100 curses.beep()
3097 curses.beep()
3101 self.report(AttributeError(_attrname(attrname)))
3098 self.report(AttributeError(_attrname(attrname)))
3102 return
3099 return
3103 self.report("sort by %s (descending)" % _attrname(attrname))
3100 self.report("sort by %s (descending)" % _attrname(attrname))
3104 def key(item):
3101 def key(item):
3105 try:
3102 try:
3106 return _getattr(item, attrname, None)
3103 return _getattr(item, attrname, None)
3107 except (KeyboardInterrupt, SystemExit):
3104 except (KeyboardInterrupt, SystemExit):
3108 raise
3105 raise
3109 except Exception:
3106 except Exception:
3110 return None
3107 return None
3111 level.sort(key, reverse=True)
3108 level.sort(key, reverse=True)
3112
3109
3113 def cmd_goto(self):
3110 def cmd_goto(self):
3114 self.startkeyboardinput("goto")
3111 self.startkeyboardinput("goto")
3115
3112
3116 def exe_goto(self):
3113 def exe_goto(self):
3117 level = self.levels[-1]
3114 level = self.levels[-1]
3118 if self.keyboardinput:
3115 if self.keyboardinput:
3119 level.moveto(level.curx, int(self.keyboardinput))
3116 level.moveto(level.curx, int(self.keyboardinput))
3120
3117
3121 def cmd_find(self):
3118 def cmd_find(self):
3122 self.startkeyboardinput("find")
3119 self.startkeyboardinput("find")
3123
3120
3124 def exe_find(self):
3121 def exe_find(self):
3125 level = self.levels[-1]
3122 level = self.levels[-1]
3126 if self.keyboardinput:
3123 if self.keyboardinput:
3127 while True:
3124 while True:
3128 cury = level.cury
3125 cury = level.cury
3129 level.moveto(level.curx, cury+1)
3126 level.moveto(level.curx, cury+1)
3130 if cury == level.cury:
3127 if cury == level.cury:
3131 curses.beep()
3128 curses.beep()
3132 break
3129 break
3133 item = level.items[level.cury].item
3130 item = level.items[level.cury].item
3134 try:
3131 try:
3135 if eval(self.keyboardinput, globals(), _AttrNamespace(item)):
3132 if eval(self.keyboardinput, globals(), _AttrNamespace(item)):
3136 break
3133 break
3137 except (KeyboardInterrupt, SystemExit):
3134 except (KeyboardInterrupt, SystemExit):
3138 raise
3135 raise
3139 except Exception, exc:
3136 except Exception, exc:
3140 self.report(exc)
3137 self.report(exc)
3141 curses.beep()
3138 curses.beep()
3142 break # break on error
3139 break # break on error
3143
3140
3144 def cmd_findbackwards(self):
3141 def cmd_findbackwards(self):
3145 self.startkeyboardinput("findbackwards")
3142 self.startkeyboardinput("findbackwards")
3146
3143
3147 def exe_findbackwards(self):
3144 def exe_findbackwards(self):
3148 level = self.levels[-1]
3145 level = self.levels[-1]
3149 if self.keyboardinput:
3146 if self.keyboardinput:
3150 while level.cury:
3147 while level.cury:
3151 level.moveto(level.curx, level.cury-1)
3148 level.moveto(level.curx, level.cury-1)
3152 item = level.items[level.cury].item
3149 item = level.items[level.cury].item
3153 try:
3150 try:
3154 if eval(self.keyboardinput, globals(), _AttrNamespace(item)):
3151 if eval(self.keyboardinput, globals(), _AttrNamespace(item)):
3155 break
3152 break
3156 except (KeyboardInterrupt, SystemExit):
3153 except (KeyboardInterrupt, SystemExit):
3157 raise
3154 raise
3158 except Exception, exc:
3155 except Exception, exc:
3159 self.report(exc)
3156 self.report(exc)
3160 curses.beep()
3157 curses.beep()
3161 break # break on error
3158 break # break on error
3162 else:
3159 else:
3163 curses.beep()
3160 curses.beep()
3164
3161
3165 def cmd_help(self):
3162 def cmd_help(self):
3166 """
3163 """
3167 The help command
3164 The help command
3168 """
3165 """
3169 for level in self.levels:
3166 for level in self.levels:
3170 if isinstance(level.input, _BrowserHelp):
3167 if isinstance(level.input, _BrowserHelp):
3171 curses.beep()
3168 curses.beep()
3172 self.report(CommandError("help already active"))
3169 self.report(CommandError("help already active"))
3173 return
3170 return
3174
3171
3175 self.enter(_BrowserHelp(self), "default")
3172 self.enter(_BrowserHelp(self), "default")
3176
3173
3177 def _dodisplay(self, scr):
3174 def _dodisplay(self, scr):
3178 """
3175 """
3179 This method is the workhorse of the browser. It handles screen
3176 This method is the workhorse of the browser. It handles screen
3180 drawing and the keyboard.
3177 drawing and the keyboard.
3181 """
3178 """
3182 self.scr = scr
3179 self.scr = scr
3183 curses.halfdelay(1)
3180 curses.halfdelay(1)
3184 footery = 2
3181 footery = 2
3185
3182
3186 keys = []
3183 keys = []
3187 for (key, cmd) in self.keymap.iteritems():
3184 for (key, cmd) in self.keymap.iteritems():
3188 if cmd == "quit":
3185 if cmd == "quit":
3189 keys.append("%s=%s" % (self.keylabel(key), cmd))
3186 keys.append("%s=%s" % (self.keylabel(key), cmd))
3190 for (key, cmd) in self.keymap.iteritems():
3187 for (key, cmd) in self.keymap.iteritems():
3191 if cmd == "help":
3188 if cmd == "help":
3192 keys.append("%s=%s" % (self.keylabel(key), cmd))
3189 keys.append("%s=%s" % (self.keylabel(key), cmd))
3193 helpmsg = " | %s" % " ".join(keys)
3190 helpmsg = " | %s" % " ".join(keys)
3194
3191
3195 scr.clear()
3192 scr.clear()
3196 msg = "Fetching first batch of objects..."
3193 msg = "Fetching first batch of objects..."
3197 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
3194 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
3198 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
3195 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
3199 scr.refresh()
3196 scr.refresh()
3200
3197
3201 lastc = -1
3198 lastc = -1
3202
3199
3203 self.levels = []
3200 self.levels = []
3204 # enter the first level
3201 # enter the first level
3205 self.enter(self.input, xiter(self.input, "default"), *self.attrs)
3202 self.enter(self.input, xiter(self.input, "default"), *self.attrs)
3206
3203
3207 self._calcheaderlines(None)
3204 self._calcheaderlines(None)
3208
3205
3209 while True:
3206 while True:
3210 level = self.levels[-1]
3207 level = self.levels[-1]
3211 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
3208 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
3212 level.mainsizey = self.scrsizey-1-self._headerlines-footery
3209 level.mainsizey = self.scrsizey-1-self._headerlines-footery
3213
3210
3214 # Paint object header
3211 # Paint object header
3215 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
3212 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
3216 lv = self.levels[i]
3213 lv = self.levels[i]
3217 posx = 0
3214 posx = 0
3218 posy = i-self._firstheaderline
3215 posy = i-self._firstheaderline
3219 endx = self.scrsizex
3216 endx = self.scrsizex
3220 if i: # not the first level
3217 if i: # not the first level
3221 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
3218 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
3222 if not self.levels[i-1].exhausted:
3219 if not self.levels[i-1].exhausted:
3223 msg += "+"
3220 msg += "+"
3224 msg += ") "
3221 msg += ") "
3225 endx -= len(msg)+1
3222 endx -= len(msg)+1
3226 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
3223 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
3227 for (style, text) in lv.header:
3224 for (style, text) in lv.header:
3228 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
3225 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
3229 if posx >= endx:
3226 if posx >= endx:
3230 break
3227 break
3231 if i:
3228 if i:
3232 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
3229 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
3233 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
3230 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
3234
3231
3235 if not level.items:
3232 if not level.items:
3236 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
3233 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
3237 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", style_error)
3234 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", style_error)
3238 scr.clrtobot()
3235 scr.clrtobot()
3239 else:
3236 else:
3240 # Paint column headers
3237 # Paint column headers
3241 scr.move(self._headerlines, 0)
3238 scr.move(self._headerlines, 0)
3242 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
3239 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
3243 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
3240 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
3244 begx = level.numbersizex+3
3241 begx = level.numbersizex+3
3245 posx = begx-level.datastartx
3242 posx = begx-level.datastartx
3246 for attrname in level.displayattrs:
3243 for attrname in level.displayattrs:
3247 strattrname = _attrname(attrname)
3244 strattrname = _attrname(attrname)
3248 cwidth = level.colwidths[attrname]
3245 cwidth = level.colwidths[attrname]
3249 header = strattrname.ljust(cwidth)
3246 header = strattrname.ljust(cwidth)
3250 if attrname == level.displayattr[1]:
3247 if attrname == level.displayattr[1]:
3251 style = self.style_colheaderhere
3248 style = self.style_colheaderhere
3252 else:
3249 else:
3253 style = self.style_colheader
3250 style = self.style_colheader
3254 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
3251 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
3255 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
3252 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
3256 if posx >= self.scrsizex:
3253 if posx >= self.scrsizex:
3257 break
3254 break
3258 else:
3255 else:
3259 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
3256 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
3260
3257
3261 # Paint rows
3258 # Paint rows
3262 posy = self._headerlines+1+level.datastarty
3259 posy = self._headerlines+1+level.datastarty
3263 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
3260 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
3264 cache = level.items[i]
3261 cache = level.items[i]
3265 if i == level.cury:
3262 if i == level.cury:
3266 style = self.style_numberhere
3263 style = self.style_numberhere
3267 else:
3264 else:
3268 style = self.style_number
3265 style = self.style_number
3269
3266
3270 posy = self._headerlines+1+i-level.datastarty
3267 posy = self._headerlines+1+i-level.datastarty
3271 posx = begx-level.datastartx
3268 posx = begx-level.datastartx
3272
3269
3273 scr.move(posy, 0)
3270 scr.move(posy, 0)
3274 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
3271 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
3275 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
3272 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
3276
3273
3277 for attrname in level.displayattrs:
3274 for attrname in level.displayattrs:
3278 cwidth = level.colwidths[attrname]
3275 cwidth = level.colwidths[attrname]
3279 try:
3276 try:
3280 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
3277 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
3281 except KeyError:
3278 except KeyError:
3282 align = 2
3279 align = 2
3283 style = style_nodata
3280 style = style_nodata
3284 padstyle = self.style_datapad
3281 padstyle = self.style_datapad
3285 sepstyle = self.style_sep
3282 sepstyle = self.style_sep
3286 if i == level.cury:
3283 if i == level.cury:
3287 padstyle = self.getstylehere(padstyle)
3284 padstyle = self.getstylehere(padstyle)
3288 sepstyle = self.getstylehere(sepstyle)
3285 sepstyle = self.getstylehere(sepstyle)
3289 if align == 2:
3286 if align == 2:
3290 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
3287 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
3291 else:
3288 else:
3292 if align == 1:
3289 if align == 1:
3293 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
3290 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
3294 elif align == 0:
3291 elif align == 0:
3295 pad1 = (cwidth-length)//2
3292 pad1 = (cwidth-length)//2
3296 pad2 = cwidth-length-len(pad1)
3293 pad2 = cwidth-length-len(pad1)
3297 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
3294 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
3298 for (style, text) in parts:
3295 for (style, text) in parts:
3299 if i == level.cury:
3296 if i == level.cury:
3300 style = self.getstylehere(style)
3297 style = self.getstylehere(style)
3301 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
3298 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
3302 if posx >= self.scrsizex:
3299 if posx >= self.scrsizex:
3303 break
3300 break
3304 if align == -1:
3301 if align == -1:
3305 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
3302 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
3306 elif align == 0:
3303 elif align == 0:
3307 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
3304 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
3308 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
3305 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
3309 else:
3306 else:
3310 scr.clrtoeol()
3307 scr.clrtoeol()
3311
3308
3312 # Add blank row headers for the rest of the screen
3309 # Add blank row headers for the rest of the screen
3313 for posy in xrange(posy+1, self.scrsizey-2):
3310 for posy in xrange(posy+1, self.scrsizey-2):
3314 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
3311 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
3315 scr.clrtoeol()
3312 scr.clrtoeol()
3316
3313
3317 posy = self.scrsizey-footery
3314 posy = self.scrsizey-footery
3318 # Display footer
3315 # Display footer
3319 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
3316 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
3320
3317
3321 if level.exhausted:
3318 if level.exhausted:
3322 flag = ""
3319 flag = ""
3323 else:
3320 else:
3324 flag = "+"
3321 flag = "+"
3325
3322
3326 endx = self.scrsizex-len(helpmsg)-1
3323 endx = self.scrsizex-len(helpmsg)-1
3327 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
3324 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
3328
3325
3329 posx = 0
3326 posx = 0
3330 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
3327 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
3331 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
3328 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
3332 try:
3329 try:
3333 item = level.items[level.cury].item
3330 item = level.items[level.cury].item
3334 except IndexError: # empty
3331 except IndexError: # empty
3335 pass
3332 pass
3336 else:
3333 else:
3337 for (nostyle, text) in xrepr(item, "footer"):
3334 for (nostyle, text) in xrepr(item, "footer"):
3338 if not isinstance(nostyle, int):
3335 if not isinstance(nostyle, int):
3339 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
3336 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
3340 if posx >= endx:
3337 if posx >= endx:
3341 break
3338 break
3342
3339
3343 attrstyle = [(style_default, "no attribute")]
3340 attrstyle = [(style_default, "no attribute")]
3344 attrname = level.displayattr[1]
3341 attrname = level.displayattr[1]
3345 if attrname is not _default and attrname is not None:
3342 if attrname is not _default and attrname is not None:
3346 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
3343 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
3347 posx += self.addstr(posy, posx, 0, endx, _attrname(attrname), self.style_footer)
3344 posx += self.addstr(posy, posx, 0, endx, _attrname(attrname), self.style_footer)
3348 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
3345 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
3349 try:
3346 try:
3350 attr = _getattr(item, attrname)
3347 attr = _getattr(item, attrname)
3351 except (SystemExit, KeyboardInterrupt):
3348 except (SystemExit, KeyboardInterrupt):
3352 raise
3349 raise
3353 except Exception, exc:
3350 except Exception, exc:
3354 attr = exc
3351 attr = exc
3355 if attr is not _default:
3352 if attr is not _default:
3356 attrstyle = xrepr(attr, "footer")
3353 attrstyle = xrepr(attr, "footer")
3357 for (nostyle, text) in attrstyle:
3354 for (nostyle, text) in attrstyle:
3358 if not isinstance(nostyle, int):
3355 if not isinstance(nostyle, int):
3359 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
3356 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
3360 if posx >= endx:
3357 if posx >= endx:
3361 break
3358 break
3362
3359
3363 try:
3360 try:
3364 # Display input prompt
3361 # Display input prompt
3365 if self.mode in self.prompts:
3362 if self.mode in self.prompts:
3366 scr.addstr(self.scrsizey-1, 0,
3363 scr.addstr(self.scrsizey-1, 0,
3367 self.prompts[self.mode] + self.keyboardinput,
3364 self.prompts[self.mode] + self.keyboardinput,
3368 self.getstyle(style_default))
3365 self.getstyle(style_default))
3369 # Display report
3366 # Display report
3370 else:
3367 else:
3371 if self._report is not None:
3368 if self._report is not None:
3372 if isinstance(self._report, Exception):
3369 if isinstance(self._report, Exception):
3373 style = self.getstyle(style_error)
3370 style = self.getstyle(style_error)
3374 if self._report.__class__.__module__ == "exceptions":
3371 if self._report.__class__.__module__ == "exceptions":
3375 msg = "%s: %s" % \
3372 msg = "%s: %s" % \
3376 (self._report.__class__.__name__, self._report)
3373 (self._report.__class__.__name__, self._report)
3377 else:
3374 else:
3378 msg = "%s.%s: %s" % \
3375 msg = "%s.%s: %s" % \
3379 (self._report.__class__.__module__,
3376 (self._report.__class__.__module__,
3380 self._report.__class__.__name__, self._report)
3377 self._report.__class__.__name__, self._report)
3381 else:
3378 else:
3382 style = self.getstyle(self.style_report)
3379 style = self.getstyle(self.style_report)
3383 msg = self._report
3380 msg = self._report
3384 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
3381 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
3385 self._report = None
3382 self._report = None
3386 else:
3383 else:
3387 scr.move(self.scrsizey-1, 0)
3384 scr.move(self.scrsizey-1, 0)
3388 except curses.error:
3385 except curses.error:
3389 # Protect against error from writing to the last line
3386 # Protect against error from writing to the last line
3390 pass
3387 pass
3391 scr.clrtoeol()
3388 scr.clrtoeol()
3392
3389
3393 # Position cursor
3390 # Position cursor
3394 if self.mode in self.prompts:
3391 if self.mode in self.prompts:
3395 scr.move(self.scrsizey-1, len(self.prompts[self.mode])+self.cursorpos)
3392 scr.move(self.scrsizey-1, len(self.prompts[self.mode])+self.cursorpos)
3396 else:
3393 else:
3397 scr.move(
3394 scr.move(
3398 1+self._headerlines+level.cury-level.datastarty,
3395 1+self._headerlines+level.cury-level.datastarty,
3399 level.numbersizex+3+level.curx-level.datastartx
3396 level.numbersizex+3+level.curx-level.datastartx
3400 )
3397 )
3401 scr.refresh()
3398 scr.refresh()
3402
3399
3403 # Check keyboard
3400 # Check keyboard
3404 while True:
3401 while True:
3405 c = scr.getch()
3402 c = scr.getch()
3406 if self.mode in self.prompts:
3403 if self.mode in self.prompts:
3407 if c in (8, 127, curses.KEY_BACKSPACE):
3404 if c in (8, 127, curses.KEY_BACKSPACE):
3408 if self.cursorpos:
3405 if self.cursorpos:
3409 self.keyboardinput = self.keyboardinput[:self.cursorpos-1] + self.keyboardinput[self.cursorpos:]
3406 self.keyboardinput = self.keyboardinput[:self.cursorpos-1] + self.keyboardinput[self.cursorpos:]
3410 self.cursorpos -= 1
3407 self.cursorpos -= 1
3411 break
3408 break
3412 else:
3409 else:
3413 curses.beep()
3410 curses.beep()
3414 elif c == curses.KEY_LEFT:
3411 elif c == curses.KEY_LEFT:
3415 if self.cursorpos:
3412 if self.cursorpos:
3416 self.cursorpos -= 1
3413 self.cursorpos -= 1
3417 break
3414 break
3418 else:
3415 else:
3419 curses.beep()
3416 curses.beep()
3420 elif c == curses.KEY_RIGHT:
3417 elif c == curses.KEY_RIGHT:
3421 if self.cursorpos < len(self.keyboardinput):
3418 if self.cursorpos < len(self.keyboardinput):
3422 self.cursorpos += 1
3419 self.cursorpos += 1
3423 break
3420 break
3424 else:
3421 else:
3425 curses.beep()
3422 curses.beep()
3426 elif c in (curses.KEY_UP, curses.KEY_DOWN): # cancel
3423 elif c in (curses.KEY_UP, curses.KEY_DOWN): # cancel
3427 self.mode = "default"
3424 self.mode = "default"
3428 break
3425 break
3429 elif c == ord("\n"):
3426 elif c == ord("\n"):
3430 self.executekeyboardinput(self.mode)
3427 self.executekeyboardinput(self.mode)
3431 break
3428 break
3432 elif c != -1:
3429 elif c != -1:
3433 try:
3430 try:
3434 c = chr(c)
3431 c = chr(c)
3435 except ValueError:
3432 except ValueError:
3436 curses.beep()
3433 curses.beep()
3437 else:
3434 else:
3438 if (self.mode == "goto" and not "0" <= c <= "9"):
3435 if (self.mode == "goto" and not "0" <= c <= "9"):
3439 curses.beep()
3436 curses.beep()
3440 else:
3437 else:
3441 self.keyboardinput = self.keyboardinput[:self.cursorpos] + c + self.keyboardinput[self.cursorpos:]
3438 self.keyboardinput = self.keyboardinput[:self.cursorpos] + c + self.keyboardinput[self.cursorpos:]
3442 self.cursorpos += 1
3439 self.cursorpos += 1
3443 break # Redisplay
3440 break # Redisplay
3444 else:
3441 else:
3445 # if no key is pressed slow down and beep again
3442 # if no key is pressed slow down and beep again
3446 if c == -1:
3443 if c == -1:
3447 self.stepx = 1.
3444 self.stepx = 1.
3448 self.stepy = 1.
3445 self.stepy = 1.
3449 self._dobeep = True
3446 self._dobeep = True
3450 else:
3447 else:
3451 # if a different key was pressed slow down and beep too
3448 # if a different key was pressed slow down and beep too
3452 if c != lastc:
3449 if c != lastc:
3453 lastc = c
3450 lastc = c
3454 self.stepx = 1.
3451 self.stepx = 1.
3455 self.stepy = 1.
3452 self.stepy = 1.
3456 self._dobeep = True
3453 self._dobeep = True
3457 cmdname = self.keymap.get(c, None)
3454 cmdname = self.keymap.get(c, None)
3458 if cmdname is None:
3455 if cmdname is None:
3459 self.report(
3456 self.report(
3460 UnassignedKeyError("Unassigned key %s" %
3457 UnassignedKeyError("Unassigned key %s" %
3461 self.keylabel(c)))
3458 self.keylabel(c)))
3462 else:
3459 else:
3463 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
3460 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
3464 if cmdfunc is None:
3461 if cmdfunc is None:
3465 self.report(
3462 self.report(
3466 UnknownCommandError("Unknown command %r" %
3463 UnknownCommandError("Unknown command %r" %
3467 (cmdname,)))
3464 (cmdname,)))
3468 elif cmdfunc():
3465 elif cmdfunc():
3469 returnvalue = self.returnvalue
3466 returnvalue = self.returnvalue
3470 self.returnvalue = None
3467 self.returnvalue = None
3471 return returnvalue
3468 return returnvalue
3472 self.stepx = self.nextstepx(self.stepx)
3469 self.stepx = self.nextstepx(self.stepx)
3473 self.stepy = self.nextstepy(self.stepy)
3470 self.stepy = self.nextstepy(self.stepy)
3474 curses.flushinp() # get rid of type ahead
3471 curses.flushinp() # get rid of type ahead
3475 break # Redisplay
3472 break # Redisplay
3476 self.scr = None
3473 self.scr = None
3477
3474
3478 def display(self):
3475 def display(self):
3479 return curses.wrapper(self._dodisplay)
3476 return curses.wrapper(self._dodisplay)
3480
3477
3481 defaultdisplay = ibrowse
3478 defaultdisplay = ibrowse
3482 __all__.append("ibrowse")
3479 __all__.append("ibrowse")
3483 else:
3480 else:
3484 # No curses (probably Windows) => use ``idump`` as the default display.
3481 # No curses (probably Windows) => use ``idump`` as the default display.
3485 defaultdisplay = idump
3482 defaultdisplay = idump
3486
3483
3487
3484
3488 # If we're running under IPython, install an IPython displayhook that
3485 # If we're running under IPython, install an IPython displayhook that
3489 # returns the object from Display.display(), else install a displayhook
3486 # returns the object from Display.display(), else install a displayhook
3490 # directly as sys.displayhook
3487 # directly as sys.displayhook
3491 try:
3488 try:
3492 from IPython import ipapi
3489 from IPython import ipapi
3493 api = ipapi.get()
3490 api = ipapi.get()
3494 except (ImportError, AttributeError):
3491 except (ImportError, AttributeError):
3495 api = None
3492 api = None
3496
3493
3497 if api is not None:
3494 if api is not None:
3498 def displayhook(self, obj):
3495 def displayhook(self, obj):
3499 if isinstance(obj, type) and issubclass(obj, Table):
3496 if isinstance(obj, type) and issubclass(obj, Table):
3500 obj = obj()
3497 obj = obj()
3501 if isinstance(obj, Table):
3498 if isinstance(obj, Table):
3502 obj = obj | defaultdisplay
3499 obj = obj | defaultdisplay
3503 if isinstance(obj, Display):
3500 if isinstance(obj, Display):
3504 return obj.display()
3501 return obj.display()
3505 else:
3502 else:
3506 raise ipapi.TryNext
3503 raise ipapi.TryNext
3507 api.set_hook("result_display", displayhook)
3504 api.set_hook("result_display", displayhook)
3508 else:
3505 else:
3509 def installdisplayhook():
3506 def installdisplayhook():
3510 _originalhook = sys.displayhook
3507 _originalhook = sys.displayhook
3511 def displayhook(obj):
3508 def displayhook(obj):
3512 if isinstance(obj, type) and issubclass(obj, Table):
3509 if isinstance(obj, type) and issubclass(obj, Table):
3513 obj = obj()
3510 obj = obj()
3514 if isinstance(obj, Table):
3511 if isinstance(obj, Table):
3515 obj = obj | defaultdisplay
3512 obj = obj | defaultdisplay
3516 if isinstance(obj, Display):
3513 if isinstance(obj, Display):
3517 return obj.display()
3514 return obj.display()
3518 else:
3515 else:
3519 _originalhook(obj)
3516 _originalhook(obj)
3520 sys.displayhook = displayhook
3517 sys.displayhook = displayhook
3521 installdisplayhook()
3518 installdisplayhook()
General Comments 0
You need to be logged in to leave comments. Login now