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