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