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