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