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