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