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