##// END OF EJS Templates
Walter's ipipe patch #8:...
vivainio -
Show More
@@ -1,3138 +1,3185 b''
1 # -*- coding: iso-8859-1 -*-
1 # -*- coding: iso-8859-1 -*-
2
2
3 """
3 """
4 ``ipipe`` provides classes to be used in an interactive Python session. Doing a
4 ``ipipe`` provides classes to be used in an interactive Python session. Doing a
5 ``from ipipe import *`` is the preferred way to do this. The name of all
5 ``from ipipe import *`` is the preferred way to do this. The name of all
6 objects imported this way starts with ``i`` to minimize collisions.
6 objects imported this way starts with ``i`` to minimize collisions.
7
7
8 ``ipipe`` supports "pipeline expressions", which is something resembling Unix
8 ``ipipe`` supports "pipeline expressions", which is something resembling Unix
9 pipes. An example is:
9 pipes. An example is:
10
10
11 >>> ienv | isort("key.lower()")
11 >>> ienv | isort("key.lower()")
12
12
13 This gives a listing of all environment variables sorted by name.
13 This gives a listing of all environment variables sorted by name.
14
14
15
15
16 There are three types of objects in a pipeline expression:
16 There are three types of objects in a pipeline expression:
17
17
18 * ``Table``s: These objects produce items. Examples are ``ls`` (listing the
18 * ``Table``s: These objects produce items. Examples are ``ls`` (listing the
19 current directory, ``ienv`` (listing environment variables), ``ipwd`` (listing
19 current directory, ``ienv`` (listing environment variables), ``ipwd`` (listing
20 user account) and ``igrp`` (listing user groups). A ``Table`` must be the
20 user account) and ``igrp`` (listing user groups). A ``Table`` must be the
21 first object in a pipe expression.
21 first object in a pipe expression.
22
22
23 * ``Pipe``s: These objects sit in the middle of a pipe expression. They
23 * ``Pipe``s: These objects sit in the middle of a pipe expression. They
24 transform the input in some way (e.g. filtering or sorting it). Examples are:
24 transform the input in some way (e.g. filtering or sorting it). Examples are:
25 ``ifilter`` (which filters the input pipe), ``isort`` (which sorts the input
25 ``ifilter`` (which filters the input pipe), ``isort`` (which sorts the input
26 pipe) and ``ieval`` (which evaluates a function or expression for each object
26 pipe) and ``ieval`` (which evaluates a function or expression for each object
27 in the input pipe).
27 in the input pipe).
28
28
29 * ``Display``s: These objects can be put as the last object in a pipeline
29 * ``Display``s: These objects can be put as the last object in a pipeline
30 expression. There are responsible for displaying the result of the pipeline
30 expression. There are responsible for displaying the result of the pipeline
31 expression. If a pipeline expression doesn't end in a display object a default
31 expression. If a pipeline expression doesn't end in a display object a default
32 display objects will be used. One example is ``browse`` which is a ``curses``
32 display objects will be used. One example is ``browse`` which is a ``curses``
33 based browser.
33 based browser.
34
34
35
35
36 Adding support for pipeline expressions to your own objects can be done through
36 Adding support for pipeline expressions to your own objects can be done through
37 three extensions points (all of them optional):
37 three extensions points (all of them optional):
38
38
39 * An object that will be displayed as a row by a ``Display`` object should
39 * An object that will be displayed as a row by a ``Display`` object should
40 implement the method ``__xattrs__(self, mode)``. This method must return a
40 implement the method ``__xattrs__(self, mode)``. This method must return a
41 sequence of attribute names. This sequence may also contain integers, which
41 sequence of attribute names. This sequence may also contain integers, which
42 will be treated as sequence indizes. Also supported is ``None``, which uses
42 will be treated as sequence indizes. Also supported is ``None``, which uses
43 the object itself and callables which will be called with the object as the
43 the object itself and callables which will be called with the object as the
44 an argument. If ``__xattrs__()`` isn't implemented ``(None,)`` will be used as
44 an argument. If ``__xattrs__()`` isn't implemented ``(None,)`` will be used as
45 the attribute sequence (i.e. the object itself (it's ``repr()`` format) will
45 the attribute sequence (i.e. the object itself (it's ``repr()`` format) will
46 be being displayed. The global function ``xattrs()`` implements this
46 be being displayed. The global function ``xattrs()`` implements this
47 functionality.
47 functionality.
48
48
49 * When an object ``foo`` is displayed in the header, footer or table cell of the
49 * When an object ``foo`` is displayed in the header, footer or table cell of the
50 browser ``foo.__xrepr__(mode)`` is called. Mode can be ``"header"`` or
50 browser ``foo.__xrepr__(mode)`` is called. Mode can be ``"header"`` or
51 ``"footer"`` for the header or footer line and ``"cell"`` for a table cell.
51 ``"footer"`` for the header or footer line and ``"cell"`` for a table cell.
52 ``__xrepr__()```must return an iterable (e.g. by being a generator) which
52 ``__xrepr__()```must return an iterable (e.g. by being a generator) which
53 produces the following items: The first item should be a tuple containing
53 produces the following items: The first item should be a tuple containing
54 the alignment (-1 left aligned, 0 centered and 1 right aligned) and whether
54 the alignment (-1 left aligned, 0 centered and 1 right aligned) and whether
55 the complete output must be displayed or if the browser is allowed to stop
55 the complete output must be displayed or if the browser is allowed to stop
56 output after enough text has been produced (e.g. a syntax highlighted text
56 output after enough text has been produced (e.g. a syntax highlighted text
57 line would use ``True``, but for a large data structure (i.e. a nested list,
57 line would use ``True``, but for a large data structure (i.e. a nested list,
58 tuple or dictionary) ``False`` would be used). The other output ``__xrepr__()``
58 tuple or dictionary) ``False`` would be used). The other output ``__xrepr__()``
59 may produce is tuples of ``Style```objects and text (which contain the text
59 may produce is tuples of ``Style```objects and text (which contain the text
60 representation of the object). If ``__xrepr__()`` recursively outputs a data
60 representation of the object). If ``__xrepr__()`` recursively outputs a data
61 structure the function ``xrepr(object, mode)`` can be used and ``"default"``
61 structure the function ``xrepr(object, mode)`` can be used and ``"default"``
62 must be passed as the mode in these calls. This in turn calls the
62 must be passed as the mode in these calls. This in turn calls the
63 ``__xrepr__()`` method on ``object`` (or uses ``repr(object)`` as the string
63 ``__xrepr__()`` method on ``object`` (or uses ``repr(object)`` as the string
64 representation if ``__xrepr__()`` doesn't exist.
64 representation if ``__xrepr__()`` doesn't exist.
65
65
66 * Objects that can be iterated by ``Pipe``s must implement the method
66 * Objects that can be iterated by ``Pipe``s must implement the method
67 ``__xiter__(self, mode)``. ``mode`` can take the following values:
67 ``__xiter__(self, mode)``. ``mode`` can take the following values:
68
68
69 - ``"default"``: This is the default value and ist always used by pipeline
69 - ``"default"``: This is the default value and ist always used by pipeline
70 expressions. Other values are only used in the browser.
70 expressions. Other values are only used in the browser.
71 - ``None``: This value is passed by the browser. The object must return an
71 - ``None``: This value is passed by the browser. The object must return an
72 iterable of ``XMode`` objects describing all modes supported by the object.
72 iterable of ``XMode`` objects describing all modes supported by the object.
73 (This should never include ``"default"`` or ``None``).
73 (This should never include ``"default"`` or ``None``).
74 - Any other value that the object supports.
74 - Any other value that the object supports.
75
75
76 The global function ``xiter()`` can be called to get such an iterator. If
76 The global function ``xiter()`` can be called to get such an iterator. If
77 the method ``_xiter__`` isn't implemented, ``xiter()`` falls back to
77 the method ``_xiter__`` isn't implemented, ``xiter()`` falls back to
78 ``__iter__``. In addition to that, dictionaries and modules receive special
78 ``__iter__``. In addition to that, dictionaries and modules receive special
79 treatment (returning an iterator over ``(key, value)`` pairs). This makes it
79 treatment (returning an iterator over ``(key, value)`` pairs). This makes it
80 possible to use dictionaries and modules in pipeline expressions, for example:
80 possible to use dictionaries and modules in pipeline expressions, for example:
81
81
82 >>> import sys
82 >>> import sys
83 >>> sys | ifilter("isinstance(value, int)") | idump
83 >>> sys | ifilter("isinstance(value, int)") | idump
84 key |value
84 key |value
85 api_version| 1012
85 api_version| 1012
86 dllhandle | 503316480
86 dllhandle | 503316480
87 hexversion | 33817328
87 hexversion | 33817328
88 maxint |2147483647
88 maxint |2147483647
89 maxunicode | 65535
89 maxunicode | 65535
90 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
90 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
91 ...
91 ...
92
92
93 Note: The expression strings passed to ``ifilter()`` and ``isort()`` can
93 Note: The expression strings passed to ``ifilter()`` and ``isort()`` can
94 refer to the object to be filtered or sorted via the variable ``_`` and to any
94 refer to the object to be filtered or sorted via the variable ``_`` and to any
95 of the attributes of the object, i.e.:
95 of the attributes of the object, i.e.:
96
96
97 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
97 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
98
98
99 does the same as
99 does the same as
100
100
101 >>> sys.modules | ifilter("value is not None") | isort("key.lower()")
101 >>> sys.modules | ifilter("value is not None") | isort("key.lower()")
102
102
103 In addition to expression strings, it's possible to pass callables (taking
103 In addition to expression strings, it's possible to pass callables (taking
104 the object as an argument) to ``ifilter()``, ``isort()`` and ``ieval()``:
104 the object as an argument) to ``ifilter()``, ``isort()`` and ``ieval()``:
105
105
106 >>> sys | ifilter(lambda _:isinstance(_.value, int)) \
106 >>> sys | ifilter(lambda _:isinstance(_.value, int)) \
107 ... | ieval(lambda _: (_.key, hex(_.value))) | idump
107 ... | ieval(lambda _: (_.key, hex(_.value))) | idump
108 0 |1
108 0 |1
109 api_version|0x3f4
109 api_version|0x3f4
110 dllhandle |0x1e000000
110 dllhandle |0x1e000000
111 hexversion |0x20402f0
111 hexversion |0x20402f0
112 maxint |0x7fffffff
112 maxint |0x7fffffff
113 maxunicode |0xffff
113 maxunicode |0xffff
114 """
114 """
115
115
116 import sys, os, os.path, stat, glob, new, csv, datetime, types
116 import sys, os, os.path, stat, glob, new, csv, datetime, types
117 import textwrap, itertools, mimetypes
117 import textwrap, itertools, mimetypes
118
118
119 try: # Python 2.3 compatibility
119 try: # Python 2.3 compatibility
120 import collections
120 import collections
121 except ImportError:
121 except ImportError:
122 deque = list
122 deque = list
123 else:
123 else:
124 deque = collections.deque
124 deque = collections.deque
125
125
126 try: # Python 2.3 compatibility
126 try: # Python 2.3 compatibility
127 set
127 set
128 except NameError:
128 except NameError:
129 import sets
129 import sets
130 set = sets.Set
130 set = sets.Set
131
131
132 try: # Python 2.3 compatibility
132 try: # Python 2.3 compatibility
133 sorted
133 sorted
134 except NameError:
134 except NameError:
135 def sorted(iterator, key=None, reverse=False):
135 def sorted(iterator, key=None, reverse=False):
136 items = list(iterator)
136 items = list(iterator)
137 if key is not None:
137 if key is not None:
138 items.sort(lambda i1, i2: cmp(key(i1), key(i2)))
138 items.sort(lambda i1, i2: cmp(key(i1), key(i2)))
139 else:
139 else:
140 items.sort()
140 items.sort()
141 if reverse:
141 if reverse:
142 items.reverse()
142 items.reverse()
143 return items
143 return items
144
144
145 try:
145 try:
146 import pwd
146 import pwd
147 except ImportError:
147 except ImportError:
148 pwd = None
148 pwd = None
149
149
150 try:
150 try:
151 import grp
151 import grp
152 except ImportError:
152 except ImportError:
153 grp = None
153 grp = None
154
154
155 try:
155 try:
156 import curses
156 import curses
157 except ImportError:
157 except ImportError:
158 curses = None
158 curses = None
159
159
160 import path
160 import path
161
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.abspath()))
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):
1785 doc = doc.strip()
1784 self.doc = doc
1786 self.doc = doc
1785
1787
1786 def __xattrs__(self, mode):
1788 def __xattrs__(self, mode):
1787 return ("name", "type", "doc", "value")
1789 return ("name", "type", "doc", "value")
1788
1790
1789
1791
1790 _ibrowse_help = """
1792 _ibrowse_help = """
1791 down
1793 down
1792 Move the cursor to the next line.
1794 Move the cursor to the next line.
1793
1795
1794 up
1796 up
1795 Move the cursor to the previous line.
1797 Move the cursor to the previous line.
1796
1798
1797 pagedown
1799 pagedown
1798 Move the cursor down one page (minus overlap).
1800 Move the cursor down one page (minus overlap).
1799
1801
1800 pageup
1802 pageup
1801 Move the cursor up one page (minus overlap).
1803 Move the cursor up one page (minus overlap).
1802
1804
1803 left
1805 left
1804 Move the cursor left.
1806 Move the cursor left.
1805
1807
1806 right
1808 right
1807 Move the cursor right.
1809 Move the cursor right.
1808
1810
1809 home
1811 home
1810 Move the cursor to the first column.
1812 Move the cursor to the first column.
1811
1813
1812 end
1814 end
1813 Move the cursor to the last column.
1815 Move the cursor to the last column.
1814
1816
1815 prevattr
1817 prevattr
1816 Move the cursor one attribute column to the left.
1818 Move the cursor one attribute column to the left.
1817
1819
1818 nextattr
1820 nextattr
1819 Move the cursor one attribute column to the right.
1821 Move the cursor one attribute column to the right.
1820
1822
1821 pick
1823 pick
1822 '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
1823 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
1824 will be available as the '_' variable.)
1826 will be available as the '_' variable.)
1825
1827
1826 pickattr
1828 pickattr
1827 '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).
1828
1830
1829 pickallattrs
1831 pickallattrs
1830 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)
1831 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.
1832
1834
1833 tooglemark
1835 tooglemark
1834 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
1835 row number).
1837 row number).
1836
1838
1837 pickmarked
1839 pickmarked
1838 'Pick' marked objects. Marked objects will be returned as a list.
1840 'Pick' marked objects. Marked objects will be returned as a list.
1839
1841
1840 pickmarkedattr
1842 pickmarkedattr
1841 '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
1842 list).
1844 list).
1843
1845
1844 enterdefault
1846 enterdefault
1845 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
1846 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
1847 'level'.
1849 'level'.
1848
1850
1849 enter
1851 enter
1850 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
1851 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'
1852 or 'enterdefault' command).
1854 or 'enterdefault' command).
1853
1855
1854 enterattr
1856 enterattr
1855 Enter the attribute under the cursor.
1857 Enter the attribute under the cursor.
1856
1858
1857 leave
1859 leave
1858 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.
1859
1861
1860 detail
1862 detail
1861 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,
1862 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
1863 than in the list view, depending on the object).
1865 than in the list view, depending on the object).
1864
1866
1865 detailattr
1867 detailattr
1866 Show a detail view of the attribute under the cursor.
1868 Show a detail view of the attribute under the cursor.
1867
1869
1868 markrange
1870 markrange
1869 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
1870 to the cursor position.
1872 to the cursor position.
1871
1873
1872 sortattrasc
1874 sortattrasc
1873 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
1874 the sort key.
1876 the sort key.
1875
1877
1876 sortattrdesc
1878 sortattrdesc
1877 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
1878 the sort key.
1880 the sort key.
1879
1881
1882 goto
1883 Jump to a row. The row number can be entered at the bottom of the screen.
1884
1880 help
1885 help
1881 This screen.
1886 This screen.
1882 """
1887 """
1883
1888
1884
1889
1885 if curses is not None:
1890 if curses is not None:
1886 class UnassignedKeyError(Exception):
1891 class UnassignedKeyError(Exception):
1887 """
1892 """
1888 Exception that is used for reporting unassigned keys.
1893 Exception that is used for reporting unassigned keys.
1889 """
1894 """
1890
1895
1891
1896
1892 class UnknownCommandError(Exception):
1897 class UnknownCommandError(Exception):
1893 """
1898 """
1894 Exception that is used for reporting unknown command (this should never
1899 Exception that is used for reporting unknown command (this should never
1895 happen).
1900 happen).
1896 """
1901 """
1897
1902
1898
1903
1899 class CommandError(Exception):
1904 class CommandError(Exception):
1900 """
1905 """
1901 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.
1902 """
1907 """
1903
1908
1904
1909
1905 class _BrowserCachedItem(object):
1910 class _BrowserCachedItem(object):
1906 # 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
1907 # marked status.
1912 # marked status.
1908 __slots__ = ("item", "marked")
1913 __slots__ = ("item", "marked")
1909
1914
1910 def __init__(self, item):
1915 def __init__(self, item):
1911 self.item = item
1916 self.item = item
1912 self.marked = False
1917 self.marked = False
1913
1918
1914
1919
1915 class _BrowserHelp(object):
1920 class _BrowserHelp(object):
1916 # This is used internally by ``ibrowse`` for displaying the help screen.
1921 # This is used internally by ``ibrowse`` for displaying the help screen.
1917 def __init__(self, browser):
1922 def __init__(self, browser):
1918 self.browser = browser
1923 self.browser = browser
1919
1924
1920 def __xrepr__(self, mode):
1925 def __xrepr__(self, mode):
1926 yield (-1, True)
1921 if mode == "header" or mode == "footer":
1927 if mode == "header" or mode == "footer":
1922 return "ibrowse help screen"
1928 yield (style_default, "ibrowse help screen")
1923 return repr(self)
1929 else:
1930 yield (style_default, repr(self))
1924
1931
1925 def __xiter__(self, mode):
1932 def __xiter__(self, mode):
1926 # Get reverse key mapping
1933 # Get reverse key mapping
1927 allkeys = {}
1934 allkeys = {}
1928 for (key, cmd) in self.browser.keymap.iteritems():
1935 for (key, cmd) in self.browser.keymap.iteritems():
1929 allkeys.setdefault(cmd, []).append(key)
1936 allkeys.setdefault(cmd, []).append(key)
1930
1937
1931 fields = ("key", "command", "description")
1938 fields = ("key", "command", "description")
1932
1939
1933 for (i, command) in enumerate(_ibrowse_help.strip().split("\n\n")):
1940 for (i, command) in enumerate(_ibrowse_help.strip().split("\n\n")):
1934 if i:
1941 if i:
1935 yield Fields(fields, key="", command="", description="")
1942 yield Fields(fields, key="", command="", description="")
1936
1943
1937 (name, description) = command.split("\n", 1)
1944 (name, description) = command.split("\n", 1)
1938 keys = allkeys.get(name, [])
1945 keys = allkeys.get(name, [])
1939 lines = textwrap.wrap(description, 50)
1946 lines = textwrap.wrap(description, 50)
1940
1947
1941 for i in xrange(max(len(keys), len(lines))):
1948 for i in xrange(max(len(keys), len(lines))):
1942 if i:
1949 if i:
1943 name = ""
1950 name = ""
1944 try:
1951 try:
1945 key = self.browser.keylabel(keys[i])
1952 key = self.browser.keylabel(keys[i])
1946 except IndexError:
1953 except IndexError:
1947 key = ""
1954 key = ""
1948 try:
1955 try:
1949 line = lines[i]
1956 line = lines[i]
1950 except IndexError:
1957 except IndexError:
1951 line = ""
1958 line = ""
1952 yield Fields(fields, key=key, command=name, description=line)
1959 yield Fields(fields, key=key, command=name, description=line)
1953
1960
1954
1961
1955 class _BrowserLevel(object):
1962 class _BrowserLevel(object):
1956 # This is used internally to store the state (iterator, fetch items,
1963 # This is used internally to store the state (iterator, fetch items,
1957 # position of cursor and screen, etc.) of one browser level
1964 # position of cursor and screen, etc.) of one browser level
1958 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
1965 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
1959 # a stack.
1966 # a stack.
1960 def __init__(self, browser, input, iterator, mainsizey, *attrs):
1967 def __init__(self, browser, input, iterator, mainsizey, *attrs):
1961 self.browser = browser
1968 self.browser = browser
1962 self.input = input
1969 self.input = input
1963 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)]
1964 # iterator for the input
1971 # iterator for the input
1965 self.iterator = iterator
1972 self.iterator = iterator
1966
1973
1967 # is the iterator exhausted?
1974 # is the iterator exhausted?
1968 self.exhausted = False
1975 self.exhausted = False
1969
1976
1970 # attributes to be display (autodetected if empty)
1977 # attributes to be display (autodetected if empty)
1971 self.attrs = attrs
1978 self.attrs = attrs
1972
1979
1973 # fetched items (+ marked flag)
1980 # fetched items (+ marked flag)
1974 self.items = deque()
1981 self.items = deque()
1975
1982
1976 # Number of marked objects
1983 # Number of marked objects
1977 self.marked = 0
1984 self.marked = 0
1978
1985
1979 # Vertical cursor position
1986 # Vertical cursor position
1980 self.cury = 0
1987 self.cury = 0
1981
1988
1982 # Horizontal cursor position
1989 # Horizontal cursor position
1983 self.curx = 0
1990 self.curx = 0
1984
1991
1985 # Index of first data column
1992 # Index of first data column
1986 self.datastartx = 0
1993 self.datastartx = 0
1987
1994
1988 # Index of first data line
1995 # Index of first data line
1989 self.datastarty = 0
1996 self.datastarty = 0
1990
1997
1991 # height of the data display area
1998 # height of the data display area
1992 self.mainsizey = mainsizey
1999 self.mainsizey = mainsizey
1993
2000
1994 # width of the data display area (changes when scrolling)
2001 # width of the data display area (changes when scrolling)
1995 self.mainsizex = 0
2002 self.mainsizex = 0
1996
2003
1997 # Size of row number (changes when scrolling)
2004 # Size of row number (changes when scrolling)
1998 self.numbersizex = 0
2005 self.numbersizex = 0
1999
2006
2000 # Attribute names to display (in this order)
2007 # Attribute names to display (in this order)
2001 self.displayattrs = []
2008 self.displayattrs = []
2002
2009
2003 # index and name of attribute under the cursor
2010 # index and name of attribute under the cursor
2004 self.displayattr = (None, _default)
2011 self.displayattr = (None, _default)
2005
2012
2006 # Maps attribute names to column widths
2013 # Maps attribute names to column widths
2007 self.colwidths = {}
2014 self.colwidths = {}
2008
2015
2009 self.fetch(mainsizey)
2016 self.fetch(mainsizey)
2010 self.calcdisplayattrs()
2017 self.calcdisplayattrs()
2011 # formatted attributes for the items on screen
2018 # formatted attributes for the items on screen
2012 # (i.e. self.items[self.datastarty:self.datastarty+self.mainsizey])
2019 # (i.e. self.items[self.datastarty:self.datastarty+self.mainsizey])
2013 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))]
2014 self.calcwidths()
2021 self.calcwidths()
2015 self.calcdisplayattr()
2022 self.calcdisplayattr()
2016
2023
2017 def fetch(self, count):
2024 def fetch(self, count):
2018 # Try to fill ``self.items`` with at least ``count`` objects.
2025 # Try to fill ``self.items`` with at least ``count`` objects.
2019 have = len(self.items)
2026 have = len(self.items)
2020 while not self.exhausted and have < count:
2027 while not self.exhausted and have < count:
2021 try:
2028 try:
2022 item = self.iterator.next()
2029 item = self.iterator.next()
2023 except StopIteration:
2030 except StopIteration:
2024 self.exhausted = True
2031 self.exhausted = True
2025 break
2032 break
2026 else:
2033 else:
2027 have += 1
2034 have += 1
2028 self.items.append(_BrowserCachedItem(item))
2035 self.items.append(_BrowserCachedItem(item))
2029
2036
2030 def calcdisplayattrs(self):
2037 def calcdisplayattrs(self):
2031 # Calculate which attributes are available from the objects that are
2038 # Calculate which attributes are available from the objects that are
2032 # currently visible on screen (and store it in ``self.displayattrs``)
2039 # currently visible on screen (and store it in ``self.displayattrs``)
2033 attrnames = set()
2040 attrnames = set()
2034 # If the browser object specifies a fixed list of attributes,
2041 # If the browser object specifies a fixed list of attributes,
2035 # simply use it.
2042 # simply use it.
2036 if self.attrs:
2043 if self.attrs:
2037 self.displayattrs = self.attrs
2044 self.displayattrs = self.attrs
2038 else:
2045 else:
2039 self.displayattrs = []
2046 self.displayattrs = []
2040 endy = min(self.datastarty+self.mainsizey, len(self.items))
2047 endy = min(self.datastarty+self.mainsizey, len(self.items))
2041 for i in xrange(self.datastarty, endy):
2048 for i in xrange(self.datastarty, endy):
2042 for attrname in xattrs(self.items[i].item, "default"):
2049 for attrname in xattrs(self.items[i].item, "default"):
2043 if attrname not in attrnames:
2050 if attrname not in attrnames:
2044 self.displayattrs.append(attrname)
2051 self.displayattrs.append(attrname)
2045 attrnames.add(attrname)
2052 attrnames.add(attrname)
2046
2053
2047 def getrow(self, i):
2054 def getrow(self, i):
2048 # Return a dictinary with the attributes for the object
2055 # Return a dictinary with the attributes for the object
2049 # ``self.items[i]``. Attribute names are taken from
2056 # ``self.items[i]``. Attribute names are taken from
2050 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
2057 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
2051 # called before.
2058 # called before.
2052 row = {}
2059 row = {}
2053 item = self.items[i].item
2060 item = self.items[i].item
2054 for attrname in self.displayattrs:
2061 for attrname in self.displayattrs:
2055 try:
2062 try:
2056 value = _getattr(item, attrname, _default)
2063 value = _getattr(item, attrname, _default)
2057 except (KeyboardInterrupt, SystemExit):
2064 except (KeyboardInterrupt, SystemExit):
2058 raise
2065 raise
2059 except Exception, exc:
2066 except Exception, exc:
2060 value = exc
2067 value = exc
2061 # only store attribute if it exists (or we got an exception)
2068 # only store attribute if it exists (or we got an exception)
2062 if value is not _default:
2069 if value is not _default:
2063 parts = []
2070 parts = []
2064 totallength = 0
2071 totallength = 0
2065 align = None
2072 align = None
2066 full = False
2073 full = False
2067 # Collect parts until we have enough
2074 # Collect parts until we have enough
2068 for part in xrepr(value, "cell"):
2075 for part in xrepr(value, "cell"):
2069 # part gives (alignment, stop)
2076 # part gives (alignment, stop)
2070 # instead of (style, text)
2077 # instead of (style, text)
2071 if isinstance(part[0], int):
2078 if isinstance(part[0], int):
2072 # only consider the first occurence
2079 # only consider the first occurence
2073 if align is None:
2080 if align is None:
2074 align = part[0]
2081 align = part[0]
2075 full = part[1]
2082 full = part[1]
2076 else:
2083 else:
2077 parts.append(part)
2084 parts.append(part)
2078 totallength += len(part[1])
2085 totallength += len(part[1])
2079 if totallength >= self.browser.maxattrlength and not full:
2086 if totallength >= self.browser.maxattrlength and not full:
2080 parts.append((style_ellisis, "..."))
2087 parts.append((style_ellisis, "..."))
2081 totallength += 3
2088 totallength += 3
2082 break
2089 break
2083 # remember alignment, length and colored parts
2090 # remember alignment, length and colored parts
2084 row[attrname] = (align, totallength, parts)
2091 row[attrname] = (align, totallength, parts)
2085 return row
2092 return row
2086
2093
2087 def calcwidths(self):
2094 def calcwidths(self):
2088 # Recalculate the displayed fields and their width.
2095 # Recalculate the displayed fields and their width.
2089 # ``calcdisplayattrs()'' must have been called and the cache
2096 # ``calcdisplayattrs()'' must have been called and the cache
2090 # for attributes of the objects on screen (``self.displayrows``)
2097 # for attributes of the objects on screen (``self.displayrows``)
2091 # must have been filled. This returns a dictionary mapping
2098 # must have been filled. This returns a dictionary mapping
2092 # colmn names to width.
2099 # colmn names to width.
2093 self.colwidths = {}
2100 self.colwidths = {}
2094 for row in self.displayrows:
2101 for row in self.displayrows:
2095 for attrname in self.displayattrs:
2102 for attrname in self.displayattrs:
2096 try:
2103 try:
2097 length = row[attrname][1]
2104 length = row[attrname][1]
2098 except KeyError:
2105 except KeyError:
2099 length = 0
2106 length = 0
2100 # always add attribute to colwidths, even if it doesn't exist
2107 # always add attribute to colwidths, even if it doesn't exist
2101 if attrname not in self.colwidths:
2108 if attrname not in self.colwidths:
2102 self.colwidths[attrname] = len(_attrname(attrname))
2109 self.colwidths[attrname] = len(_attrname(attrname))
2103 newwidth = max(self.colwidths[attrname], length)
2110 newwidth = max(self.colwidths[attrname], length)
2104 self.colwidths[attrname] = newwidth
2111 self.colwidths[attrname] = newwidth
2105
2112
2106 # How many characters do we need to paint the item number?
2113 # How many characters do we need to paint the item number?
2107 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
2114 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
2108 # How must space have we got to display data?
2115 # How must space have we got to display data?
2109 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
2116 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
2110 # width of all columns
2117 # width of all columns
2111 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
2118 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
2112
2119
2113 def calcdisplayattr(self):
2120 def calcdisplayattr(self):
2114 # 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
2115 # information in ``self.displayattr``.
2122 # information in ``self.displayattr``.
2116 pos = 0
2123 pos = 0
2117 for (i, attrname) in enumerate(self.displayattrs):
2124 for (i, attrname) in enumerate(self.displayattrs):
2118 if pos+self.colwidths[attrname] >= self.curx:
2125 if pos+self.colwidths[attrname] >= self.curx:
2119 self.displayattr = (i, attrname)
2126 self.displayattr = (i, attrname)
2120 break
2127 break
2121 pos += self.colwidths[attrname]+1
2128 pos += self.colwidths[attrname]+1
2122 else:
2129 else:
2123 self.displayattr = (None, _default)
2130 self.displayattr = (None, _default)
2124
2131
2125 def moveto(self, x, y, refresh=False):
2132 def moveto(self, x, y, refresh=False):
2126 # Move the cursor to the position ``(x,y)`` (in data coordinates,
2133 # Move the cursor to the position ``(x,y)`` (in data coordinates,
2127 # not in screen coordinates). If ``refresh`` is true, all cached
2134 # not in screen coordinates). If ``refresh`` is true, all cached
2128 # values will be recalculated (e.g. because the list has been
2135 # values will be recalculated (e.g. because the list has been
2129 # resorted, so screen positions etc. are no longer valid).
2136 # resorted, so screen positions etc. are no longer valid).
2130 olddatastarty = self.datastarty
2137 olddatastarty = self.datastarty
2131 oldx = self.curx
2138 oldx = self.curx
2132 oldy = self.cury
2139 oldy = self.cury
2133 x = int(x+0.5)
2140 x = int(x+0.5)
2134 y = int(y+0.5)
2141 y = int(y+0.5)
2135 newx = x # remember where we wanted to move
2142 newx = x # remember where we wanted to move
2136 newy = y # remember where we wanted to move
2143 newy = y # remember where we wanted to move
2137
2144
2138 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
2145 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
2139 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
2146 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
2140
2147
2141 # 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
2142 if y < 0:
2149 if y < 0:
2143 y = 0
2150 y = 0
2144 self.fetch(y+scrollbordery+1) # try to get more items
2151 self.fetch(y+scrollbordery+1) # try to get more items
2145 if y >= len(self.items):
2152 if y >= len(self.items):
2146 y = max(0, len(self.items)-1)
2153 y = max(0, len(self.items)-1)
2147
2154
2148 # Make sure that the cursor stays on screen vertically
2155 # Make sure that the cursor stays on screen vertically
2149 if y < self.datastarty+scrollbordery:
2156 if y < self.datastarty+scrollbordery:
2150 self.datastarty = max(0, y-scrollbordery)
2157 self.datastarty = max(0, y-scrollbordery)
2151 elif y >= self.datastarty+self.mainsizey-scrollbordery:
2158 elif y >= self.datastarty+self.mainsizey-scrollbordery:
2152 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
2159 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
2153 len(self.items)-self.mainsizey))
2160 len(self.items)-self.mainsizey))
2154
2161
2155 if refresh: # Do we need to refresh the complete display?
2162 if refresh: # Do we need to refresh the complete display?
2156 self.calcdisplayattrs()
2163 self.calcdisplayattrs()
2157 endy = min(self.datastarty+self.mainsizey, len(self.items))
2164 endy = min(self.datastarty+self.mainsizey, len(self.items))
2158 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
2165 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
2159 self.calcwidths()
2166 self.calcwidths()
2160 # Did we scroll vertically => update displayrows
2167 # Did we scroll vertically => update displayrows
2161 # and various other attributes
2168 # and various other attributes
2162 elif self.datastarty != olddatastarty:
2169 elif self.datastarty != olddatastarty:
2163 # Recalculate which attributes we have to display
2170 # Recalculate which attributes we have to display
2164 olddisplayattrs = self.displayattrs
2171 olddisplayattrs = self.displayattrs
2165 self.calcdisplayattrs()
2172 self.calcdisplayattrs()
2166 # If there are new attributes, recreate the cache
2173 # If there are new attributes, recreate the cache
2167 if self.displayattrs != olddisplayattrs:
2174 if self.displayattrs != olddisplayattrs:
2168 endy = min(self.datastarty+self.mainsizey, len(self.items))
2175 endy = min(self.datastarty+self.mainsizey, len(self.items))
2169 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
2176 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
2170 elif self.datastarty<olddatastarty: # we did scroll up
2177 elif self.datastarty<olddatastarty: # we did scroll up
2171 # drop rows from the end
2178 # drop rows from the end
2172 del self.displayrows[self.datastarty-olddatastarty:]
2179 del self.displayrows[self.datastarty-olddatastarty:]
2173 # fetch new items
2180 # fetch new items
2174 for i in xrange(olddatastarty-1,
2181 for i in xrange(olddatastarty-1,
2175 self.datastarty-1, -1):
2182 self.datastarty-1, -1):
2176 try:
2183 try:
2177 row = self.getrow(i)
2184 row = self.getrow(i)
2178 except IndexError:
2185 except IndexError:
2179 # we didn't have enough objects to fill the screen
2186 # we didn't have enough objects to fill the screen
2180 break
2187 break
2181 self.displayrows.insert(0, row)
2188 self.displayrows.insert(0, row)
2182 else: # we did scroll down
2189 else: # we did scroll down
2183 # drop rows from the start
2190 # drop rows from the start
2184 del self.displayrows[:self.datastarty-olddatastarty]
2191 del self.displayrows[:self.datastarty-olddatastarty]
2185 # fetch new items
2192 # fetch new items
2186 for i in xrange(olddatastarty+self.mainsizey,
2193 for i in xrange(olddatastarty+self.mainsizey,
2187 self.datastarty+self.mainsizey):
2194 self.datastarty+self.mainsizey):
2188 try:
2195 try:
2189 row = self.getrow(i)
2196 row = self.getrow(i)
2190 except IndexError:
2197 except IndexError:
2191 # we didn't have enough objects to fill the screen
2198 # we didn't have enough objects to fill the screen
2192 break
2199 break
2193 self.displayrows.append(row)
2200 self.displayrows.append(row)
2194 self.calcwidths()
2201 self.calcwidths()
2195
2202
2196 # 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
2197 if x < 0:
2204 if x < 0:
2198 x = 0
2205 x = 0
2199 elif x >= self.datasizex:
2206 elif x >= self.datasizex:
2200 x = max(0, self.datasizex-1)
2207 x = max(0, self.datasizex-1)
2201
2208
2202 # Make sure that the cursor stays on screen horizontally
2209 # Make sure that the cursor stays on screen horizontally
2203 if x < self.datastartx+scrollborderx:
2210 if x < self.datastartx+scrollborderx:
2204 self.datastartx = max(0, x-scrollborderx)
2211 self.datastartx = max(0, x-scrollborderx)
2205 elif x >= self.datastartx+self.mainsizex-scrollborderx:
2212 elif x >= self.datastartx+self.mainsizex-scrollborderx:
2206 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
2213 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
2207 self.datasizex-self.mainsizex))
2214 self.datasizex-self.mainsizex))
2208
2215
2209 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
2210 self.browser.beep()
2217 self.browser.beep()
2211 else:
2218 else:
2212 self.curx = x
2219 self.curx = x
2213 self.cury = y
2220 self.cury = y
2214 self.calcdisplayattr()
2221 self.calcdisplayattr()
2215
2222
2216 def sort(self, key, reverse=False):
2223 def sort(self, key, reverse=False):
2217 """
2224 """
2218 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
2219 ``reverse`` is true the sort order is reversed.
2226 ``reverse`` is true the sort order is reversed.
2220 """
2227 """
2221 curitem = self.items[self.cury] # Remember where the cursor is now
2228 curitem = self.items[self.cury] # Remember where the cursor is now
2222
2229
2223 # Sort items
2230 # Sort items
2224 def realkey(item):
2231 def realkey(item):
2225 return key(item.item)
2232 return key(item.item)
2226 self.items = deque(sorted(self.items, key=realkey, reverse=reverse))
2233 self.items = deque(sorted(self.items, key=realkey, reverse=reverse))
2227
2234
2228 # Find out where the object under the cursor went
2235 # Find out where the object under the cursor went
2229 cury = self.cury
2236 cury = self.cury
2230 for (i, item) in enumerate(self.items):
2237 for (i, item) in enumerate(self.items):
2231 if item is curitem:
2238 if item is curitem:
2232 cury = i
2239 cury = i
2233 break
2240 break
2234
2241
2235 self.moveto(self.curx, cury, refresh=True)
2242 self.moveto(self.curx, cury, refresh=True)
2236
2243
2237
2244
2238 class ibrowse(Display):
2245 class ibrowse(Display):
2239 # Show this many lines from the previous screen when paging horizontally
2246 # Show this many lines from the previous screen when paging horizontally
2240 pageoverlapx = 1
2247 pageoverlapx = 1
2241
2248
2242 # Show this many lines from the previous screen when paging vertically
2249 # Show this many lines from the previous screen when paging vertically
2243 pageoverlapy = 1
2250 pageoverlapy = 1
2244
2251
2245 # 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
2246 # away from the left or right screen edge
2253 # away from the left or right screen edge
2247 scrollborderx = 10
2254 scrollborderx = 10
2248
2255
2249 # 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
2250 # away from the top or bottom screen edge
2257 # away from the top or bottom screen edge
2251 scrollbordery = 5
2258 scrollbordery = 5
2252
2259
2253 # Accelerate by this factor when scrolling horizontally
2260 # Accelerate by this factor when scrolling horizontally
2254 acceleratex = 1.05
2261 acceleratex = 1.05
2255
2262
2256 # Accelerate by this factor when scrolling vertically
2263 # Accelerate by this factor when scrolling vertically
2257 acceleratey = 1.05
2264 acceleratey = 1.05
2258
2265
2259 # The maximum horizontal scroll speed
2266 # The maximum horizontal scroll speed
2260 # (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)
2261 maxspeedx = 0.5
2268 maxspeedx = 0.5
2262
2269
2263 # The maximum vertical scroll speed
2270 # The maximum vertical scroll speed
2264 # (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)
2265 maxspeedy = 0.5
2272 maxspeedy = 0.5
2266
2273
2267 # The maximum number of header lines for browser level
2274 # The maximum number of header lines for browser level
2268 # if the nesting is deeper, only the innermost levels are displayed
2275 # if the nesting is deeper, only the innermost levels are displayed
2269 maxheaders = 5
2276 maxheaders = 5
2270
2277
2271 # The approximate maximum length of a column entry
2278 # The approximate maximum length of a column entry
2272 maxattrlength = 200
2279 maxattrlength = 200
2273
2280
2274 # Styles for various parts of the GUI
2281 # Styles for various parts of the GUI
2275 style_objheadertext = Style(COLOR_WHITE, COLOR_BLACK, A_BOLD|A_REVERSE)
2282 style_objheadertext = Style(COLOR_WHITE, COLOR_BLACK, A_BOLD|A_REVERSE)
2276 style_objheadernumber = Style(COLOR_WHITE, COLOR_BLUE, A_BOLD|A_REVERSE)
2283 style_objheadernumber = Style(COLOR_WHITE, COLOR_BLUE, A_BOLD|A_REVERSE)
2277 style_objheaderobject = Style(COLOR_WHITE, COLOR_BLACK, A_REVERSE)
2284 style_objheaderobject = Style(COLOR_WHITE, COLOR_BLACK, A_REVERSE)
2278 style_colheader = Style(COLOR_BLUE, COLOR_WHITE, A_REVERSE)
2285 style_colheader = Style(COLOR_BLUE, COLOR_WHITE, A_REVERSE)
2279 style_colheaderhere = Style(COLOR_GREEN, COLOR_BLACK, A_BOLD|A_REVERSE)
2286 style_colheaderhere = Style(COLOR_GREEN, COLOR_BLACK, A_BOLD|A_REVERSE)
2280 style_colheadersep = Style(COLOR_BLUE, COLOR_BLACK, A_REVERSE)
2287 style_colheadersep = Style(COLOR_BLUE, COLOR_BLACK, A_REVERSE)
2281 style_number = Style(COLOR_BLUE, COLOR_WHITE, A_REVERSE)
2288 style_number = Style(COLOR_BLUE, COLOR_WHITE, A_REVERSE)
2282 style_numberhere = Style(COLOR_GREEN, COLOR_BLACK, A_BOLD|A_REVERSE)
2289 style_numberhere = Style(COLOR_GREEN, COLOR_BLACK, A_BOLD|A_REVERSE)
2283 style_sep = Style(COLOR_BLUE, COLOR_BLACK)
2290 style_sep = Style(COLOR_BLUE, COLOR_BLACK)
2284 style_data = Style(COLOR_WHITE, COLOR_BLACK)
2291 style_data = Style(COLOR_WHITE, COLOR_BLACK)
2285 style_datapad = Style(COLOR_BLUE, COLOR_BLACK, A_BOLD)
2292 style_datapad = Style(COLOR_BLUE, COLOR_BLACK, A_BOLD)
2286 style_footer = Style(COLOR_BLACK, COLOR_WHITE)
2293 style_footer = Style(COLOR_BLACK, COLOR_WHITE)
2287 style_report = Style(COLOR_WHITE, COLOR_BLACK)
2294 style_report = Style(COLOR_WHITE, COLOR_BLACK)
2288
2295
2289 # Column separator in header
2296 # Column separator in header
2290 headersepchar = "|"
2297 headersepchar = "|"
2291
2298
2292 # Character for padding data cell entries
2299 # Character for padding data cell entries
2293 datapadchar = "."
2300 datapadchar = "."
2294
2301
2295 # Column separator in data area
2302 # Column separator in data area
2296 datasepchar = "|"
2303 datasepchar = "|"
2297
2304
2298 # 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)
2299 nodatachar = "-"
2306 nodatachar = "-"
2300
2307
2308 # Prompt for the goto command
2309 prompt_goto = "goto object #: "
2310
2301 # Maps curses key codes to "function" names
2311 # Maps curses key codes to "function" names
2302 keymap = {
2312 keymap = {
2303 ord("q"): "quit",
2313 ord("q"): "quit",
2304 curses.KEY_UP: "up",
2314 curses.KEY_UP: "up",
2305 curses.KEY_DOWN: "down",
2315 curses.KEY_DOWN: "down",
2306 curses.KEY_PPAGE: "pageup",
2316 curses.KEY_PPAGE: "pageup",
2307 curses.KEY_NPAGE: "pagedown",
2317 curses.KEY_NPAGE: "pagedown",
2308 curses.KEY_LEFT: "left",
2318 curses.KEY_LEFT: "left",
2309 curses.KEY_RIGHT: "right",
2319 curses.KEY_RIGHT: "right",
2310 curses.KEY_HOME: "home",
2320 curses.KEY_HOME: "home",
2311 curses.KEY_END: "end",
2321 curses.KEY_END: "end",
2312 ord("<"): "prevattr",
2322 ord("<"): "prevattr",
2313 0x1b: "prevattr", # SHIFT-TAB
2323 0x1b: "prevattr", # SHIFT-TAB
2314 ord(">"): "nextattr",
2324 ord(">"): "nextattr",
2315 ord("\t"):"nextattr", # TAB
2325 ord("\t"):"nextattr", # TAB
2316 ord("p"): "pick",
2326 ord("p"): "pick",
2317 ord("P"): "pickattr",
2327 ord("P"): "pickattr",
2318 ord("C"): "pickallattrs",
2328 ord("C"): "pickallattrs",
2319 ord("m"): "pickmarked",
2329 ord("m"): "pickmarked",
2320 ord("M"): "pickmarkedattr",
2330 ord("M"): "pickmarkedattr",
2321 ord("\n"): "enterdefault",
2331 ord("\n"): "enterdefault",
2322 # FIXME: What's happening here?
2332 # FIXME: What's happening here?
2323 8: "leave",
2333 8: "leave",
2324 127: "leave",
2334 127: "leave",
2325 curses.KEY_BACKSPACE: "leave",
2335 curses.KEY_BACKSPACE: "leave",
2326 ord("x"): "leave",
2336 ord("x"): "leave",
2327 ord("h"): "help",
2337 ord("h"): "help",
2328 ord("e"): "enter",
2338 ord("e"): "enter",
2329 ord("E"): "enterattr",
2339 ord("E"): "enterattr",
2330 ord("d"): "detail",
2340 ord("d"): "detail",
2331 ord("D"): "detailattr",
2341 ord("D"): "detailattr",
2332 ord(" "): "tooglemark",
2342 ord(" "): "tooglemark",
2333 ord("r"): "markrange",
2343 ord("r"): "markrange",
2334 ord("v"): "sortattrasc",
2344 ord("v"): "sortattrasc",
2335 ord("V"): "sortattrdesc",
2345 ord("V"): "sortattrdesc",
2346 ord("g"): "goto",
2336 }
2347 }
2337
2348
2338 def __init__(self, *attrs):
2349 def __init__(self, *attrs):
2339 """
2350 """
2340 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
2341 of attributes that will be displayed in the browser, otherwise
2352 of attributes that will be displayed in the browser, otherwise
2342 these will be determined by the objects on screen.
2353 these will be determined by the objects on screen.
2343 """
2354 """
2344 self.attrs = attrs
2355 self.attrs = attrs
2345
2356
2346 # Stack of browser levels
2357 # Stack of browser levels
2347 self.levels = []
2358 self.levels = []
2348 # how many colums to scroll (Changes when accelerating)
2359 # how many colums to scroll (Changes when accelerating)
2349 self.stepx = 1.
2360 self.stepx = 1.
2350
2361
2351 # how many rows to scroll (Changes when accelerating)
2362 # how many rows to scroll (Changes when accelerating)
2352 self.stepy = 1.
2363 self.stepy = 1.
2353
2364
2354 # 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``
2355 # 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
2356 # multiple beeps).
2367 # multiple beeps).
2357 self._dobeep = True
2368 self._dobeep = True
2358
2369
2359 # Cache for registered ``curses`` colors and styles.
2370 # Cache for registered ``curses`` colors and styles.
2360 self._styles = {}
2371 self._styles = {}
2361 self._colors = {}
2372 self._colors = {}
2362 self._maxcolor = 1
2373 self._maxcolor = 1
2363
2374
2364 # 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
2365 # we have, but with an upper bound)
2376 # we have, but with an upper bound)
2366 self._headerlines = 1
2377 self._headerlines = 1
2367
2378
2368 # Index of first header line
2379 # Index of first header line
2369 self._firstheaderline = 0
2380 self._firstheaderline = 0
2370
2381
2371 # curses window
2382 # curses window
2372 self.scr = None
2383 self.scr = None
2373 # report in the footer line (error, executed command etc.)
2384 # report in the footer line (error, executed command etc.)
2374 self._report = None
2385 self._report = None
2375
2386
2376 # value to be returned to the caller (set by commands)
2387 # value to be returned to the caller (set by commands)
2377 self.returnvalue = None
2388 self.returnvalue = None
2378
2389
2390 # The mode the browser is in
2391 # e.g. normal browsing or entering an argument for a command
2392 self.mode = "default"
2393
2394 # The partially entered row number for the goto command
2395 self.goto = ""
2396
2379 def nextstepx(self, step):
2397 def nextstepx(self, step):
2380 """
2398 """
2381 Accelerate horizontally.
2399 Accelerate horizontally.
2382 """
2400 """
2383 return max(1., min(step*self.acceleratex,
2401 return max(1., min(step*self.acceleratex,
2384 self.maxspeedx*self.levels[-1].mainsizex))
2402 self.maxspeedx*self.levels[-1].mainsizex))
2385
2403
2386 def nextstepy(self, step):
2404 def nextstepy(self, step):
2387 """
2405 """
2388 Accelerate vertically.
2406 Accelerate vertically.
2389 """
2407 """
2390 return max(1., min(step*self.acceleratey,
2408 return max(1., min(step*self.acceleratey,
2391 self.maxspeedy*self.levels[-1].mainsizey))
2409 self.maxspeedy*self.levels[-1].mainsizey))
2392
2410
2393 def getstyle(self, style):
2411 def getstyle(self, style):
2394 """
2412 """
2395 Register the ``style`` with ``curses`` or get it from the cache,
2413 Register the ``style`` with ``curses`` or get it from the cache,
2396 if it has been registered before.
2414 if it has been registered before.
2397 """
2415 """
2398 try:
2416 try:
2399 return self._styles[style.fg, style.bg, style.attrs]
2417 return self._styles[style.fg, style.bg, style.attrs]
2400 except KeyError:
2418 except KeyError:
2401 attrs = 0
2419 attrs = 0
2402 for b in A2CURSES:
2420 for b in A2CURSES:
2403 if style.attrs & b:
2421 if style.attrs & b:
2404 attrs |= A2CURSES[b]
2422 attrs |= A2CURSES[b]
2405 try:
2423 try:
2406 color = self._colors[style.fg, style.bg]
2424 color = self._colors[style.fg, style.bg]
2407 except KeyError:
2425 except KeyError:
2408 curses.init_pair(
2426 curses.init_pair(
2409 self._maxcolor,
2427 self._maxcolor,
2410 COLOR2CURSES[style.fg],
2428 COLOR2CURSES[style.fg],
2411 COLOR2CURSES[style.bg]
2429 COLOR2CURSES[style.bg]
2412 )
2430 )
2413 color = curses.color_pair(self._maxcolor)
2431 color = curses.color_pair(self._maxcolor)
2414 self._colors[style.fg, style.bg] = color
2432 self._colors[style.fg, style.bg] = color
2415 self._maxcolor += 1
2433 self._maxcolor += 1
2416 c = color | attrs
2434 c = color | attrs
2417 self._styles[style.fg, style.bg, style.attrs] = c
2435 self._styles[style.fg, style.bg, style.attrs] = c
2418 return c
2436 return c
2419
2437
2420 def format(self, value):
2438 def format(self, value):
2421 """
2439 """
2422 Formats one attribute and returns an ``(alignment, string, style)``
2440 Formats one attribute and returns an ``(alignment, string, style)``
2423 tuple.
2441 tuple.
2424 """
2442 """
2425 if value is None:
2443 if value is None:
2426 return (-1, repr(value), style_type_none)
2444 return (-1, repr(value), style_type_none)
2427 elif isinstance(value, str):
2445 elif isinstance(value, str):
2428 return (-1, repr(value.expandtabs(tab))[1:-1], style_default)
2446 return (-1, repr(value.expandtabs(tab))[1:-1], style_default)
2429 elif isinstance(value, unicode):
2447 elif isinstance(value, unicode):
2430 return (-1, repr(value.expandtabs(tab))[2:-1], style_default)
2448 return (-1, repr(value.expandtabs(tab))[2:-1], style_default)
2431 elif isinstance(value, datetime.datetime):
2449 elif isinstance(value, datetime.datetime):
2432 # Don't use strftime() here, as this requires year >= 1900
2450 # Don't use strftime() here, as this requires year >= 1900
2433 return (-1, "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
2451 return (-1, "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
2434 (value.year, value.month, value.day,
2452 (value.year, value.month, value.day,
2435 value.hour, value.minute, value.second,
2453 value.hour, value.minute, value.second,
2436 value.microsecond),
2454 value.microsecond),
2437 style_type_datetime)
2455 style_type_datetime)
2438 elif isinstance(value, datetime.date):
2456 elif isinstance(value, datetime.date):
2439 return (-1, "%04d-%02d-%02d" % \
2457 return (-1, "%04d-%02d-%02d" % \
2440 (value.year, value.month, value.day),
2458 (value.year, value.month, value.day),
2441 style_type_datetime)
2459 style_type_datetime)
2442 elif isinstance(value, datetime.time):
2460 elif isinstance(value, datetime.time):
2443 return (-1, "%02d:%02d:%02d.%06d" % \
2461 return (-1, "%02d:%02d:%02d.%06d" % \
2444 (value.hour, value.minute, value.second,
2462 (value.hour, value.minute, value.second,
2445 value.microsecond),
2463 value.microsecond),
2446 style_type_datetime)
2464 style_type_datetime)
2447 elif isinstance(value, datetime.timedelta):
2465 elif isinstance(value, datetime.timedelta):
2448 return (-1, repr(value), style_type_datetime)
2466 return (-1, repr(value), style_type_datetime)
2449 elif isinstance(value, bool):
2467 elif isinstance(value, bool):
2450 return (-1, repr(value), style_type_bool)
2468 return (-1, repr(value), style_type_bool)
2451 elif isinstance(value, (int, long, float)):
2469 elif isinstance(value, (int, long, float)):
2452 return (1, repr(value), style_type_number)
2470 return (1, repr(value), style_type_number)
2453 elif isinstance(value, complex):
2471 elif isinstance(value, complex):
2454 return (-1, repr(value), style_type_number)
2472 return (-1, repr(value), style_type_number)
2455 elif isinstance(value, Exception):
2473 elif isinstance(value, Exception):
2456 if value.__class__.__module__ == "exceptions":
2474 if value.__class__.__module__ == "exceptions":
2457 value = "%s: %s" % (value.__class__.__name__, value)
2475 value = "%s: %s" % (value.__class__.__name__, value)
2458 else:
2476 else:
2459 value = "%s.%s: %s" % \
2477 value = "%s.%s: %s" % \
2460 (value.__class__.__module__, value.__class__.__name__,
2478 (value.__class__.__module__, value.__class__.__name__,
2461 value)
2479 value)
2462 return (-1, value, style_error)
2480 return (-1, value, style_error)
2463 return (-1, repr(value), style_default)
2481 return (-1, repr(value), style_default)
2464
2482
2465 def addstr(self, y, x, begx, endx, text, style):
2483 def addstr(self, y, x, begx, endx, text, style):
2466 """
2484 """
2467 A version of ``curses.addstr()`` that can handle ``x`` coordinates
2485 A version of ``curses.addstr()`` that can handle ``x`` coordinates
2468 that are outside the screen.
2486 that are outside the screen.
2469 """
2487 """
2470 text2 = text[max(0, begx-x):max(0, endx-x)]
2488 text2 = text[max(0, begx-x):max(0, endx-x)]
2471 if text2:
2489 if text2:
2472 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
2490 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
2473 return len(text)
2491 return len(text)
2474
2492
2475 def addchr(self, y, x, begx, endx, c, l, style):
2493 def addchr(self, y, x, begx, endx, c, l, style):
2476 x0 = max(x, begx)
2494 x0 = max(x, begx)
2477 x1 = min(x+l, endx)
2495 x1 = min(x+l, endx)
2478 if x1>x0:
2496 if x1>x0:
2479 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
2497 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
2480 return l
2498 return l
2481
2499
2482 def _calcheaderlines(self, levels):
2500 def _calcheaderlines(self, levels):
2483 # 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
2484 # ``levels`` browser levels
2502 # ``levels`` browser levels
2485 if levels is None:
2503 if levels is None:
2486 levels = len(self.levels)
2504 levels = len(self.levels)
2487 self._headerlines = min(self.maxheaders, levels)
2505 self._headerlines = min(self.maxheaders, levels)
2488 self._firstheaderline = levels-self._headerlines
2506 self._firstheaderline = levels-self._headerlines
2489
2507
2490 def getstylehere(self, style):
2508 def getstylehere(self, style):
2491 """
2509 """
2492 Return a style for displaying the original style ``style``
2510 Return a style for displaying the original style ``style``
2493 in the row the cursor is on.
2511 in the row the cursor is on.
2494 """
2512 """
2495 return Style(style.fg, style.bg, style.attrs | A_BOLD)
2513 return Style(style.fg, style.bg, style.attrs | A_BOLD)
2496
2514
2497 def report(self, msg):
2515 def report(self, msg):
2498 """
2516 """
2499 Store the message ``msg`` for display below the footer line. This
2517 Store the message ``msg`` for display below the footer line. This
2500 will be displayed as soon as the screen is redrawn.
2518 will be displayed as soon as the screen is redrawn.
2501 """
2519 """
2502 self._report = msg
2520 self._report = msg
2503
2521
2504 def enter(self, item, mode, *attrs):
2522 def enter(self, item, mode, *attrs):
2505 """
2523 """
2506 Enter the object ``item`` in the mode ``mode``. If ``attrs`` is
2524 Enter the object ``item`` in the mode ``mode``. If ``attrs`` is
2507 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.
2508 """
2526 """
2509 try:
2527 try:
2510 iterator = xiter(item, mode)
2528 iterator = xiter(item, mode)
2511 except (KeyboardInterrupt, SystemExit):
2529 except (KeyboardInterrupt, SystemExit):
2512 raise
2530 raise
2513 except Exception, exc:
2531 except Exception, exc:
2514 curses.beep()
2532 curses.beep()
2515 self.report(exc)
2533 self.report(exc)
2516 else:
2534 else:
2517 self._calcheaderlines(len(self.levels)+1)
2535 self._calcheaderlines(len(self.levels)+1)
2518 level = _BrowserLevel(
2536 level = _BrowserLevel(
2519 self,
2537 self,
2520 item,
2538 item,
2521 iterator,
2539 iterator,
2522 self.scrsizey-1-self._headerlines-2,
2540 self.scrsizey-1-self._headerlines-2,
2523 *attrs
2541 *attrs
2524 )
2542 )
2525 self.levels.append(level)
2543 self.levels.append(level)
2526
2544
2527 def keylabel(self, keycode):
2545 def keylabel(self, keycode):
2528 """
2546 """
2529 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
2530 help screen and in reports about unassigned keys).
2548 help screen and in reports about unassigned keys).
2531 """
2549 """
2532 if keycode <= 0xff:
2550 if keycode <= 0xff:
2533 specialsnames = {
2551 specialsnames = {
2534 ord("\n"): "RETURN",
2552 ord("\n"): "RETURN",
2535 ord(" "): "SPACE",
2553 ord(" "): "SPACE",
2536 ord("\t"): "TAB",
2554 ord("\t"): "TAB",
2537 ord("\x7f"): "DELETE",
2555 ord("\x7f"): "DELETE",
2538 ord("\x08"): "BACKSPACE",
2556 ord("\x08"): "BACKSPACE",
2539 }
2557 }
2540 if keycode in specialsnames:
2558 if keycode in specialsnames:
2541 return specialsnames[keycode]
2559 return specialsnames[keycode]
2542 return repr(chr(keycode))
2560 return repr(chr(keycode))
2543 for name in dir(curses):
2561 for name in dir(curses):
2544 if name.startswith("KEY_") and getattr(curses, name) == keycode:
2562 if name.startswith("KEY_") and getattr(curses, name) == keycode:
2545 return name
2563 return name
2546 return str(keycode)
2564 return str(keycode)
2547
2565
2548 def beep(self, force=False):
2566 def beep(self, force=False):
2549 if force or self._dobeep:
2567 if force or self._dobeep:
2550 curses.beep()
2568 curses.beep()
2551 # 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)
2552 self._dobeep = False
2570 self._dobeep = False
2553
2571
2554 def cmd_quit(self):
2572 def cmd_quit(self):
2555 self.returnvalue = None
2573 self.returnvalue = None
2556 return True
2574 return True
2557
2575
2558 def cmd_up(self):
2576 def cmd_up(self):
2559 level = self.levels[-1]
2577 level = self.levels[-1]
2560 self.report("up")
2578 self.report("up")
2561 level.moveto(level.curx, level.cury-self.stepy)
2579 level.moveto(level.curx, level.cury-self.stepy)
2562
2580
2563 def cmd_down(self):
2581 def cmd_down(self):
2564 level = self.levels[-1]
2582 level = self.levels[-1]
2565 self.report("down")
2583 self.report("down")
2566 level.moveto(level.curx, level.cury+self.stepy)
2584 level.moveto(level.curx, level.cury+self.stepy)
2567
2585
2568 def cmd_pageup(self):
2586 def cmd_pageup(self):
2569 level = self.levels[-1]
2587 level = self.levels[-1]
2570 self.report("page up")
2588 self.report("page up")
2571 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
2589 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
2572
2590
2573 def cmd_pagedown(self):
2591 def cmd_pagedown(self):
2574 level = self.levels[-1]
2592 level = self.levels[-1]
2575 self.report("page down")
2593 self.report("page down")
2576 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
2594 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
2577
2595
2578 def cmd_left(self):
2596 def cmd_left(self):
2579 level = self.levels[-1]
2597 level = self.levels[-1]
2580 self.report("left")
2598 self.report("left")
2581 level.moveto(level.curx-self.stepx, level.cury)
2599 level.moveto(level.curx-self.stepx, level.cury)
2582
2600
2583 def cmd_right(self):
2601 def cmd_right(self):
2584 level = self.levels[-1]
2602 level = self.levels[-1]
2585 self.report("right")
2603 self.report("right")
2586 level.moveto(level.curx+self.stepx, level.cury)
2604 level.moveto(level.curx+self.stepx, level.cury)
2587
2605
2588 def cmd_home(self):
2606 def cmd_home(self):
2589 level = self.levels[-1]
2607 level = self.levels[-1]
2590 self.report("home")
2608 self.report("home")
2591 level.moveto(0, level.cury)
2609 level.moveto(0, level.cury)
2592
2610
2593 def cmd_end(self):
2611 def cmd_end(self):
2594 level = self.levels[-1]
2612 level = self.levels[-1]
2595 self.report("end")
2613 self.report("end")
2596 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
2614 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
2597
2615
2598 def cmd_prevattr(self):
2616 def cmd_prevattr(self):
2599 level = self.levels[-1]
2617 level = self.levels[-1]
2600 if level.displayattr[0] is None or level.displayattr[0] == 0:
2618 if level.displayattr[0] is None or level.displayattr[0] == 0:
2601 self.beep()
2619 self.beep()
2602 else:
2620 else:
2603 self.report("prevattr")
2621 self.report("prevattr")
2604 pos = 0
2622 pos = 0
2605 for (i, attrname) in enumerate(level.displayattrs):
2623 for (i, attrname) in enumerate(level.displayattrs):
2606 if i == level.displayattr[0]-1:
2624 if i == level.displayattr[0]-1:
2607 break
2625 break
2608 pos += level.colwidths[attrname] + 1
2626 pos += level.colwidths[attrname] + 1
2609 level.moveto(pos, level.cury)
2627 level.moveto(pos, level.cury)
2610
2628
2611 def cmd_nextattr(self):
2629 def cmd_nextattr(self):
2612 level = self.levels[-1]
2630 level = self.levels[-1]
2613 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:
2614 self.beep()
2632 self.beep()
2615 else:
2633 else:
2616 self.report("nextattr")
2634 self.report("nextattr")
2617 pos = 0
2635 pos = 0
2618 for (i, attrname) in enumerate(level.displayattrs):
2636 for (i, attrname) in enumerate(level.displayattrs):
2619 if i == level.displayattr[0]+1:
2637 if i == level.displayattr[0]+1:
2620 break
2638 break
2621 pos += level.colwidths[attrname] + 1
2639 pos += level.colwidths[attrname] + 1
2622 level.moveto(pos, level.cury)
2640 level.moveto(pos, level.cury)
2623
2641
2624 def cmd_pick(self):
2642 def cmd_pick(self):
2625 level = self.levels[-1]
2643 level = self.levels[-1]
2626 self.returnvalue = level.items[level.cury].item
2644 self.returnvalue = level.items[level.cury].item
2627 return True
2645 return True
2628
2646
2629 def cmd_pickattr(self):
2647 def cmd_pickattr(self):
2630 level = self.levels[-1]
2648 level = self.levels[-1]
2631 attrname = level.displayattr[1]
2649 attrname = level.displayattr[1]
2632 if attrname is _default:
2650 if attrname is _default:
2633 curses.beep()
2651 curses.beep()
2634 self.report(AttributeError(_attrname(attrname)))
2652 self.report(AttributeError(_attrname(attrname)))
2635 return
2653 return
2636 attr = _getattr(level.items[level.cury].item, attrname)
2654 attr = _getattr(level.items[level.cury].item, attrname)
2637 if attr is _default:
2655 if attr is _default:
2638 curses.beep()
2656 curses.beep()
2639 self.report(AttributeError(_attrname(attrname)))
2657 self.report(AttributeError(_attrname(attrname)))
2640 else:
2658 else:
2641 self.returnvalue = attr
2659 self.returnvalue = attr
2642 return True
2660 return True
2643
2661
2644 def cmd_pickallattrs(self):
2662 def cmd_pickallattrs(self):
2645 level = self.levels[-1]
2663 level = self.levels[-1]
2646 attrname = level.displayattr[1]
2664 attrname = level.displayattr[1]
2647 if attrname is _default:
2665 if attrname is _default:
2648 curses.beep()
2666 curses.beep()
2649 self.report(AttributeError(_attrname(attrname)))
2667 self.report(AttributeError(_attrname(attrname)))
2650 return
2668 return
2651 result = []
2669 result = []
2652 for cache in level.items:
2670 for cache in level.items:
2653 attr = _getattr(cache.item, attrname)
2671 attr = _getattr(cache.item, attrname)
2654 if attr is not _default:
2672 if attr is not _default:
2655 result.append(attr)
2673 result.append(attr)
2656 self.returnvalue = result
2674 self.returnvalue = result
2657 return True
2675 return True
2658
2676
2659 def cmd_pickmarked(self):
2677 def cmd_pickmarked(self):
2660 level = self.levels[-1]
2678 level = self.levels[-1]
2661 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]
2662 return True
2680 return True
2663
2681
2664 def cmd_pickmarkedattr(self):
2682 def cmd_pickmarkedattr(self):
2665 level = self.levels[-1]
2683 level = self.levels[-1]
2666 attrname = level.displayattr[1]
2684 attrname = level.displayattr[1]
2667 if attrname is _default:
2685 if attrname is _default:
2668 curses.beep()
2686 curses.beep()
2669 self.report(AttributeError(_attrname(attrname)))
2687 self.report(AttributeError(_attrname(attrname)))
2670 return
2688 return
2671 result = []
2689 result = []
2672 for cache in level.items:
2690 for cache in level.items:
2673 if cache.marked:
2691 if cache.marked:
2674 attr = _getattr(cache.item, attrname)
2692 attr = _getattr(cache.item, attrname)
2675 if attr is not _default:
2693 if attr is not _default:
2676 result.append(attr)
2694 result.append(attr)
2677 self.returnvalue = result
2695 self.returnvalue = result
2678 return True
2696 return True
2679
2697
2680 def cmd_markrange(self):
2698 def cmd_markrange(self):
2681 level = self.levels[-1]
2699 level = self.levels[-1]
2682 self.report("markrange")
2700 self.report("markrange")
2683 start = None
2701 start = None
2684 if level.items:
2702 if level.items:
2685 for i in xrange(level.cury, -1, -1):
2703 for i in xrange(level.cury, -1, -1):
2686 if level.items[i].marked:
2704 if level.items[i].marked:
2687 start = i
2705 start = i
2688 break
2706 break
2689 if start is None:
2707 if start is None:
2690 self.report(CommandError("no mark before cursor"))
2708 self.report(CommandError("no mark before cursor"))
2691 curses.beep()
2709 curses.beep()
2692 else:
2710 else:
2693 for i in xrange(start, level.cury+1):
2711 for i in xrange(start, level.cury+1):
2694 cache = level.items[i]
2712 cache = level.items[i]
2695 if not cache.marked:
2713 if not cache.marked:
2696 cache.marked = True
2714 cache.marked = True
2697 level.marked += 1
2715 level.marked += 1
2698
2716
2699 def cmd_enterdefault(self):
2717 def cmd_enterdefault(self):
2700 level = self.levels[-1]
2718 level = self.levels[-1]
2701 try:
2719 try:
2702 item = level.items[level.cury].item
2720 item = level.items[level.cury].item
2703 except IndexError:
2721 except IndexError:
2704 self.report(CommandError("No object"))
2722 self.report(CommandError("No object"))
2705 curses.beep()
2723 curses.beep()
2706 else:
2724 else:
2707 self.report("entering object (default mode)...")
2725 self.report("entering object (default mode)...")
2708 self.enter(item, "default")
2726 self.enter(item, "default")
2709
2727
2710 def cmd_leave(self):
2728 def cmd_leave(self):
2711 self.report("leave")
2729 self.report("leave")
2712 if len(self.levels) > 1:
2730 if len(self.levels) > 1:
2713 self._calcheaderlines(len(self.levels)-1)
2731 self._calcheaderlines(len(self.levels)-1)
2714 self.levels.pop(-1)
2732 self.levels.pop(-1)
2715 else:
2733 else:
2716 self.report(CommandError("This is the last level"))
2734 self.report(CommandError("This is the last level"))
2717 curses.beep()
2735 curses.beep()
2718
2736
2719 def cmd_enter(self):
2737 def cmd_enter(self):
2720 level = self.levels[-1]
2738 level = self.levels[-1]
2721 try:
2739 try:
2722 item = level.items[level.cury].item
2740 item = level.items[level.cury].item
2723 except IndexError:
2741 except IndexError:
2724 self.report(CommandError("No object"))
2742 self.report(CommandError("No object"))
2725 curses.beep()
2743 curses.beep()
2726 else:
2744 else:
2727 self.report("entering object...")
2745 self.report("entering object...")
2728 self.enter(item, None)
2746 self.enter(item, None)
2729
2747
2730 def cmd_enterattr(self):
2748 def cmd_enterattr(self):
2731 level = self.levels[-1]
2749 level = self.levels[-1]
2732 attrname = level.displayattr[1]
2750 attrname = level.displayattr[1]
2733 if attrname is _default:
2751 if attrname is _default:
2734 curses.beep()
2752 curses.beep()
2735 self.report(AttributeError(_attrname(attrname)))
2753 self.report(AttributeError(_attrname(attrname)))
2736 return
2754 return
2737 try:
2755 try:
2738 item = level.items[level.cury].item
2756 item = level.items[level.cury].item
2739 except IndexError:
2757 except IndexError:
2740 self.report(CommandError("No object"))
2758 self.report(CommandError("No object"))
2741 curses.beep()
2759 curses.beep()
2742 else:
2760 else:
2743 attr = _getattr(item, attrname)
2761 attr = _getattr(item, attrname)
2744 if attr is _default:
2762 if attr is _default:
2745 self.report(AttributeError(_attrname(attrname)))
2763 self.report(AttributeError(_attrname(attrname)))
2746 else:
2764 else:
2747 self.report("entering object attribute %s..." % _attrname(attrname))
2765 self.report("entering object attribute %s..." % _attrname(attrname))
2748 self.enter(attr, None)
2766 self.enter(attr, None)
2749
2767
2750 def cmd_detail(self):
2768 def cmd_detail(self):
2751 level = self.levels[-1]
2769 level = self.levels[-1]
2752 try:
2770 try:
2753 item = level.items[level.cury].item
2771 item = level.items[level.cury].item
2754 except IndexError:
2772 except IndexError:
2755 self.report(CommandError("No object"))
2773 self.report(CommandError("No object"))
2756 curses.beep()
2774 curses.beep()
2757 else:
2775 else:
2758 self.report("entering detail view for object...")
2776 self.report("entering detail view for object...")
2759 self.enter(item, "detail")
2777 self.enter(item, "detail")
2760
2778
2761 def cmd_detailattr(self):
2779 def cmd_detailattr(self):
2762 level = self.levels[-1]
2780 level = self.levels[-1]
2763 attrname = level.displayattr[1]
2781 attrname = level.displayattr[1]
2764 if attrname is _default:
2782 if attrname is _default:
2765 curses.beep()
2783 curses.beep()
2766 self.report(AttributeError(_attrname(attrname)))
2784 self.report(AttributeError(_attrname(attrname)))
2767 return
2785 return
2768 try:
2786 try:
2769 item = level.items[level.cury].item
2787 item = level.items[level.cury].item
2770 except IndexError:
2788 except IndexError:
2771 self.report(CommandError("No object"))
2789 self.report(CommandError("No object"))
2772 curses.beep()
2790 curses.beep()
2773 else:
2791 else:
2774 attr = _getattr(item, attrname)
2792 attr = _getattr(item, attrname)
2775 if attr is _default:
2793 if attr is _default:
2776 self.report(AttributeError(_attrname(attrname)))
2794 self.report(AttributeError(_attrname(attrname)))
2777 else:
2795 else:
2778 self.report("entering detail view for attribute...")
2796 self.report("entering detail view for attribute...")
2779 self.enter(attr, "detail")
2797 self.enter(attr, "detail")
2780
2798
2781 def cmd_tooglemark(self):
2799 def cmd_tooglemark(self):
2782 level = self.levels[-1]
2800 level = self.levels[-1]
2783 self.report("toggle mark")
2801 self.report("toggle mark")
2784 try:
2802 try:
2785 item = level.items[level.cury]
2803 item = level.items[level.cury]
2786 except IndexError: # no items?
2804 except IndexError: # no items?
2787 pass
2805 pass
2788 else:
2806 else:
2789 if item.marked:
2807 if item.marked:
2790 item.marked = False
2808 item.marked = False
2791 level.marked -= 1
2809 level.marked -= 1
2792 else:
2810 else:
2793 item.marked = True
2811 item.marked = True
2794 level.marked += 1
2812 level.marked += 1
2795
2813
2796 def cmd_sortattrasc(self):
2814 def cmd_sortattrasc(self):
2797 level = self.levels[-1]
2815 level = self.levels[-1]
2798 attrname = level.displayattr[1]
2816 attrname = level.displayattr[1]
2799 if attrname is _default:
2817 if attrname is _default:
2800 curses.beep()
2818 curses.beep()
2801 self.report(AttributeError(_attrname(attrname)))
2819 self.report(AttributeError(_attrname(attrname)))
2802 return
2820 return
2803 self.report("sort by %s (ascending)" % _attrname(attrname))
2821 self.report("sort by %s (ascending)" % _attrname(attrname))
2804 def key(item):
2822 def key(item):
2805 try:
2823 try:
2806 return _getattr(item, attrname, None)
2824 return _getattr(item, attrname, None)
2807 except (KeyboardInterrupt, SystemExit):
2825 except (KeyboardInterrupt, SystemExit):
2808 raise
2826 raise
2809 except Exception:
2827 except Exception:
2810 return None
2828 return None
2811 level.sort(key)
2829 level.sort(key)
2812
2830
2813 def cmd_sortattrdesc(self):
2831 def cmd_sortattrdesc(self):
2814 level = self.levels[-1]
2832 level = self.levels[-1]
2815 attrname = level.displayattr[1]
2833 attrname = level.displayattr[1]
2816 if attrname is _default:
2834 if attrname is _default:
2817 curses.beep()
2835 curses.beep()
2818 self.report(AttributeError(_attrname(attrname)))
2836 self.report(AttributeError(_attrname(attrname)))
2819 return
2837 return
2820 self.report("sort by %s (descending)" % _attrname(attrname))
2838 self.report("sort by %s (descending)" % _attrname(attrname))
2821 def key(item):
2839 def key(item):
2822 try:
2840 try:
2823 return _getattr(item, attrname, None)
2841 return _getattr(item, attrname, None)
2824 except (KeyboardInterrupt, SystemExit):
2842 except (KeyboardInterrupt, SystemExit):
2825 raise
2843 raise
2826 except Exception:
2844 except Exception:
2827 return None
2845 return None
2828 level.sort(key, reverse=True)
2846 level.sort(key, reverse=True)
2829
2847
2848 def cmd_goto(self):
2849 self.mode = "goto"
2850 self.goto = ""
2851
2830 def cmd_help(self):
2852 def cmd_help(self):
2831 """
2853 """
2832 The help command
2854 The help command
2833 """
2855 """
2834 for level in self.levels:
2856 for level in self.levels:
2835 if isinstance(level.input, _BrowserHelp):
2857 if isinstance(level.input, _BrowserHelp):
2836 curses.beep()
2858 curses.beep()
2837 self.report(CommandError("help already active"))
2859 self.report(CommandError("help already active"))
2838 return
2860 return
2839
2861
2840 self.enter(_BrowserHelp(self), "default")
2862 self.enter(_BrowserHelp(self), "default")
2841
2863
2842 def _dodisplay(self, scr):
2864 def _dodisplay(self, scr):
2843 """
2865 """
2844 This method is the workhorse of the browser. It handles screen
2866 This method is the workhorse of the browser. It handles screen
2845 drawing and the keyboard.
2867 drawing and the keyboard.
2846 """
2868 """
2847 self.scr = scr
2869 self.scr = scr
2848 curses.halfdelay(1)
2870 curses.halfdelay(1)
2849 footery = 2
2871 footery = 2
2850
2872
2851 keys = []
2873 keys = []
2852 for (key, cmd) in self.keymap.iteritems():
2874 for (key, cmd) in self.keymap.iteritems():
2853 if cmd == "quit":
2875 if cmd == "quit":
2854 keys.append("%s=%s" % (self.keylabel(key), cmd))
2876 keys.append("%s=%s" % (self.keylabel(key), cmd))
2855 for (key, cmd) in self.keymap.iteritems():
2877 for (key, cmd) in self.keymap.iteritems():
2856 if cmd == "help":
2878 if cmd == "help":
2857 keys.append("%s=%s" % (self.keylabel(key), cmd))
2879 keys.append("%s=%s" % (self.keylabel(key), cmd))
2858 helpmsg = " | %s" % " ".join(keys)
2880 helpmsg = " | %s" % " ".join(keys)
2859
2881
2860 scr.clear()
2882 scr.clear()
2861 msg = "Fetching first batch of objects..."
2883 msg = "Fetching first batch of objects..."
2862 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
2884 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
2863 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
2885 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
2864 scr.refresh()
2886 scr.refresh()
2865
2887
2866 lastc = -1
2888 lastc = -1
2867
2889
2868 self.levels = []
2890 self.levels = []
2869 # enter the first level
2891 # enter the first level
2870 self.enter(self.input, xiter(self.input, "default"), *self.attrs)
2892 self.enter(self.input, xiter(self.input, "default"), *self.attrs)
2871
2893
2872 self._calcheaderlines(None)
2894 self._calcheaderlines(None)
2873
2895
2874 while True:
2896 while True:
2875 level = self.levels[-1]
2897 level = self.levels[-1]
2876 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
2898 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
2877 level.mainsizey = self.scrsizey-1-self._headerlines-footery
2899 level.mainsizey = self.scrsizey-1-self._headerlines-footery
2878
2900
2879 # Paint object header
2901 # Paint object header
2880 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
2902 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
2881 lv = self.levels[i]
2903 lv = self.levels[i]
2882 posx = 0
2904 posx = 0
2883 posy = i-self._firstheaderline
2905 posy = i-self._firstheaderline
2884 endx = self.scrsizex
2906 endx = self.scrsizex
2885 if i: # not the first level
2907 if i: # not the first level
2886 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))
2887 if not self.levels[i-1].exhausted:
2909 if not self.levels[i-1].exhausted:
2888 msg += "+"
2910 msg += "+"
2889 msg += ") "
2911 msg += ") "
2890 endx -= len(msg)+1
2912 endx -= len(msg)+1
2891 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)
2892 for (style, text) in lv.header:
2914 for (style, text) in lv.header:
2893 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
2915 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
2894 if posx >= endx:
2916 if posx >= endx:
2895 break
2917 break
2896 if i:
2918 if i:
2897 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)
2898 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)
2899
2921
2900 if not level.items:
2922 if not level.items:
2901 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)
2902 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)
2903 scr.clrtobot()
2925 scr.clrtobot()
2904 else:
2926 else:
2905 # Paint column headers
2927 # Paint column headers
2906 scr.move(self._headerlines, 0)
2928 scr.move(self._headerlines, 0)
2907 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
2929 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
2908 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
2930 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
2909 begx = level.numbersizex+3
2931 begx = level.numbersizex+3
2910 posx = begx-level.datastartx
2932 posx = begx-level.datastartx
2911 for attrname in level.displayattrs:
2933 for attrname in level.displayattrs:
2912 strattrname = _attrname(attrname)
2934 strattrname = _attrname(attrname)
2913 cwidth = level.colwidths[attrname]
2935 cwidth = level.colwidths[attrname]
2914 header = strattrname.ljust(cwidth)
2936 header = strattrname.ljust(cwidth)
2915 if attrname == level.displayattr[1]:
2937 if attrname == level.displayattr[1]:
2916 style = self.style_colheaderhere
2938 style = self.style_colheaderhere
2917 else:
2939 else:
2918 style = self.style_colheader
2940 style = self.style_colheader
2919 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
2941 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
2920 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)
2921 if posx >= self.scrsizex:
2943 if posx >= self.scrsizex:
2922 break
2944 break
2923 else:
2945 else:
2924 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
2946 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
2925
2947
2926 # Paint rows
2948 # Paint rows
2927 posy = self._headerlines+1+level.datastarty
2949 posy = self._headerlines+1+level.datastarty
2928 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))):
2929 cache = level.items[i]
2951 cache = level.items[i]
2930 if i == level.cury:
2952 if i == level.cury:
2931 style = self.style_numberhere
2953 style = self.style_numberhere
2932 else:
2954 else:
2933 style = self.style_number
2955 style = self.style_number
2934
2956
2935 posy = self._headerlines+1+i-level.datastarty
2957 posy = self._headerlines+1+i-level.datastarty
2936 posx = begx-level.datastartx
2958 posx = begx-level.datastartx
2937
2959
2938 scr.move(posy, 0)
2960 scr.move(posy, 0)
2939 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))
2940 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
2962 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
2941
2963
2942 for attrname in level.displayattrs:
2964 for attrname in level.displayattrs:
2943 cwidth = level.colwidths[attrname]
2965 cwidth = level.colwidths[attrname]
2944 try:
2966 try:
2945 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
2967 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
2946 except KeyError:
2968 except KeyError:
2947 align = 2
2969 align = 2
2948 style = style_nodata
2970 style = style_nodata
2949 padstyle = self.style_datapad
2971 padstyle = self.style_datapad
2950 sepstyle = self.style_sep
2972 sepstyle = self.style_sep
2951 if i == level.cury:
2973 if i == level.cury:
2952 padstyle = self.getstylehere(padstyle)
2974 padstyle = self.getstylehere(padstyle)
2953 sepstyle = self.getstylehere(sepstyle)
2975 sepstyle = self.getstylehere(sepstyle)
2954 if align == 2:
2976 if align == 2:
2955 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)
2956 else:
2978 else:
2957 if align == 1:
2979 if align == 1:
2958 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)
2959 elif align == 0:
2981 elif align == 0:
2960 pad1 = (cwidth-length)//2
2982 pad1 = (cwidth-length)//2
2961 pad2 = cwidth-length-len(pad1)
2983 pad2 = cwidth-length-len(pad1)
2962 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)
2963 for (style, text) in parts:
2985 for (style, text) in parts:
2964 if i == level.cury:
2986 if i == level.cury:
2965 style = self.getstylehere(style)
2987 style = self.getstylehere(style)
2966 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
2988 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
2967 if posx >= self.scrsizex:
2989 if posx >= self.scrsizex:
2968 break
2990 break
2969 if align == -1:
2991 if align == -1:
2970 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)
2971 elif align == 0:
2993 elif align == 0:
2972 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)
2973 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
2995 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
2974 else:
2996 else:
2975 scr.clrtoeol()
2997 scr.clrtoeol()
2976
2998
2977 # Add blank row headers for the rest of the screen
2999 # Add blank row headers for the rest of the screen
2978 for posy in xrange(posy+1, self.scrsizey-2):
3000 for posy in xrange(posy+1, self.scrsizey-2):
2979 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))
2980 scr.clrtoeol()
3002 scr.clrtoeol()
2981
3003
2982 posy = self.scrsizey-footery
3004 posy = self.scrsizey-footery
2983 # Display footer
3005 # Display footer
2984 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
3006 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
2985
3007
2986 if level.exhausted:
3008 if level.exhausted:
2987 flag = ""
3009 flag = ""
2988 else:
3010 else:
2989 flag = "+"
3011 flag = "+"
2990
3012
2991 endx = self.scrsizex-len(helpmsg)-1
3013 endx = self.scrsizex-len(helpmsg)-1
2992 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
3014 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
2993
3015
2994 posx = 0
3016 posx = 0
2995 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)
2996 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
3018 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
2997 try:
3019 try:
2998 item = level.items[level.cury].item
3020 item = level.items[level.cury].item
2999 except IndexError: # empty
3021 except IndexError: # empty
3000 pass
3022 pass
3001 else:
3023 else:
3002 for (nostyle, text) in xrepr(item, "footer"):
3024 for (nostyle, text) in xrepr(item, "footer"):
3003 if not isinstance(nostyle, int):
3025 if not isinstance(nostyle, int):
3004 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
3026 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
3005 if posx >= endx:
3027 if posx >= endx:
3006 break
3028 break
3029
3030 attrstyle = [(style_default, "no attribute")]
3007 attrname = level.displayattr[1]
3031 attrname = level.displayattr[1]
3008 if attrname is not _default and attrname is not None:
3032 if attrname is not _default and attrname is not None:
3009 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
3033 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
3010 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)
3011 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
3035 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
3012 try:
3036 try:
3013 attr = _getattr(item, attrname)
3037 attr = _getattr(item, attrname)
3014 except (SystemExit, KeyboardInterrupt):
3038 except (SystemExit, KeyboardInterrupt):
3015 raise
3039 raise
3016 except Exception, exc:
3040 except Exception, exc:
3017 attr = exc
3041 attr = exc
3018 for (nostyle, text) in xrepr(attr, "footer"):
3042 if attr is not _default:
3043 attrstyle = xrepr(attr, "footer")
3044 for (nostyle, text) in attrstyle:
3019 if not isinstance(nostyle, int):
3045 if not isinstance(nostyle, int):
3020 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
3046 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
3021 if posx >= endx:
3047 if posx >= endx:
3022 break
3048 break
3023
3049
3024 #else:
3050 try:
3025 #msg += ": %s > no attribute" % xrepr(level.items[level.cury].item, "footer")
3051 # Display goto input prompt
3026 #self.addstr(posy, 1, 1, self.scrsizex-len(helpmsg)-1, msg, self.style_footer)
3052 if self.mode == "goto":
3027
3053 scr.addstr(self.scrsizey-1, 0, self.prompt_goto + self.goto, self.getstyle(style_default))
3028 # Display report
3054 # Display report
3029 if self._report is not None:
3030 if isinstance(self._report, Exception):
3031 style = self.getstyle(style_error)
3032 if self._report.__class__.__module__ == "exceptions":
3033 msg = "%s: %s" % \
3034 (self._report.__class__.__name__, self._report)
3035 else:
3036 msg = "%s.%s: %s" % \
3037 (self._report.__class__.__module__,
3038 self._report.__class__.__name__, self._report)
3039 else:
3055 else:
3040 style = self.getstyle(self.style_report)
3056 if self._report is not None:
3041 msg = self._report
3057 if isinstance(self._report, Exception):
3042 try:
3058 style = self.getstyle(style_error)
3043 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
3059 if self._report.__class__.__module__ == "exceptions":
3044 except curses.err:
3060 msg = "%s: %s" % \
3045 # Protect against error from writing to the last line
3061 (self._report.__class__.__name__, self._report)
3046 pass
3062 else:
3047 self._report = None
3063 msg = "%s.%s: %s" % \
3048 else:
3064 (self._report.__class__.__module__,
3049 scr.move(self.scrsizey-1, 0)
3065 self._report.__class__.__name__, self._report)
3066 else:
3067 style = self.getstyle(self.style_report)
3068 msg = self._report
3069 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
3070 self._report = None
3071 else:
3072 scr.move(self.scrsizey-1, 0)
3073 except curses.error:
3074 # Protect against error from writing to the last line
3075 pass
3050 scr.clrtoeol()
3076 scr.clrtoeol()
3051
3077
3052 # Position cursor
3078 # Position cursor
3053 scr.move(
3079 if self.mode == "goto":
3054 1+self._headerlines+level.cury-level.datastarty,
3080 scr.move(self.scrsizey-1, len(self.prompt_goto)+len(self.goto))
3055 level.numbersizex+3+level.curx-level.datastartx
3081 else:
3056 )
3082 scr.move(
3083 1+self._headerlines+level.cury-level.datastarty,
3084 level.numbersizex+3+level.curx-level.datastartx
3085 )
3057 scr.refresh()
3086 scr.refresh()
3058
3087
3059 # Check keyboard
3088 # Check keyboard
3060 while True:
3089 while True:
3061 c = scr.getch()
3090 c = scr.getch()
3062 # if no key is pressed slow down and beep again
3091 if self.mode == "goto":
3063 if c == -1:
3092 if ord("0") <= c <= ord("9"):
3064 self.stepx = 1.
3093 self.goto += chr(c)
3065 self.stepy = 1.
3094 break # Redisplay
3066 self._dobeep = True
3095 elif c in (8, 127, curses.KEY_BACKSPACE, ord("x")):
3096 if self.goto:
3097 self.goto = self.goto[:-1]
3098 break
3099 else:
3100 curses.beep()
3101 elif c == ord("\n"):
3102 self.mode = "default"
3103 if self.goto:
3104 level.moveto(level.curx, int(self.goto))
3105 break
3106 else:
3107 curses.beep()
3067 else:
3108 else:
3068 # if a different key was pressed slow down and beep too
3109 # if no key is pressed slow down and beep again
3069 if c != lastc:
3110 if c == -1:
3070 lastc = c
3071 self.stepx = 1.
3111 self.stepx = 1.
3072 self.stepy = 1.
3112 self.stepy = 1.
3073 self._dobeep = True
3113 self._dobeep = True
3074 cmdname = self.keymap.get(c, None)
3075 if cmdname is None:
3076 self.report(
3077 UnassignedKeyError("Unassigned key %s" %
3078 self.keylabel(c)))
3079 else:
3114 else:
3080 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
3115 # if a different key was pressed slow down and beep too
3081 if cmdfunc is None:
3116 if c != lastc:
3117 lastc = c
3118 self.stepx = 1.
3119 self.stepy = 1.
3120 self._dobeep = True
3121 cmdname = self.keymap.get(c, None)
3122 if cmdname is None:
3082 self.report(
3123 self.report(
3083 UnknownCommandError("Unknown command %r" %
3124 UnassignedKeyError("Unassigned key %s" %
3084 (cmdname,)))
3125 self.keylabel(c)))
3085 elif cmdfunc():
3126 else:
3086 returnvalue = self.returnvalue
3127 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
3087 self.returnvalue = None
3128 if cmdfunc is None:
3088 return returnvalue
3129 self.report(
3089 self.stepx = self.nextstepx(self.stepx)
3130 UnknownCommandError("Unknown command %r" %
3090 self.stepy = self.nextstepy(self.stepy)
3131 (cmdname,)))
3091 curses.flushinp() # get rid of type ahead
3132 elif cmdfunc():
3092 break # Redisplay
3133 returnvalue = self.returnvalue
3134 self.returnvalue = None
3135 return returnvalue
3136 self.stepx = self.nextstepx(self.stepx)
3137 self.stepy = self.nextstepy(self.stepy)
3138 curses.flushinp() # get rid of type ahead
3139 break # Redisplay
3093 self.scr = None
3140 self.scr = None
3094
3141
3095 def display(self):
3142 def display(self):
3096 return curses.wrapper(self._dodisplay)
3143 return curses.wrapper(self._dodisplay)
3097
3144
3098 defaultdisplay = ibrowse
3145 defaultdisplay = ibrowse
3099 __all__.append("ibrowse")
3146 __all__.append("ibrowse")
3100 else:
3147 else:
3101 # No curses (probably Windows) => use ``idump`` as the default display.
3148 # No curses (probably Windows) => use ``idump`` as the default display.
3102 defaultdisplay = idump
3149 defaultdisplay = idump
3103
3150
3104
3151
3105 # If we're running under IPython, install an IPython displayhook that
3152 # If we're running under IPython, install an IPython displayhook that
3106 # returns the object from Display.display(), else install a displayhook
3153 # returns the object from Display.display(), else install a displayhook
3107 # directly as sys.displayhook
3154 # directly as sys.displayhook
3108 try:
3155 try:
3109 from IPython import ipapi
3156 from IPython import ipapi
3110 api = ipapi.get()
3157 api = ipapi.get()
3111 except (ImportError, AttributeError):
3158 except (ImportError, AttributeError):
3112 api = None
3159 api = None
3113
3160
3114 if api is not None:
3161 if api is not None:
3115 def displayhook(self, obj):
3162 def displayhook(self, obj):
3116 if isinstance(obj, type) and issubclass(obj, Table):
3163 if isinstance(obj, type) and issubclass(obj, Table):
3117 obj = obj()
3164 obj = obj()
3118 if isinstance(obj, Table):
3165 if isinstance(obj, Table):
3119 obj = obj | defaultdisplay
3166 obj = obj | defaultdisplay
3120 if isinstance(obj, Display):
3167 if isinstance(obj, Display):
3121 return obj.display()
3168 return obj.display()
3122 else:
3169 else:
3123 raise ipapi.TryNext
3170 raise ipapi.TryNext
3124 api.set_hook("result_display", displayhook)
3171 api.set_hook("result_display", displayhook)
3125 else:
3172 else:
3126 def installdisplayhook():
3173 def installdisplayhook():
3127 _originalhook = sys.displayhook
3174 _originalhook = sys.displayhook
3128 def displayhook(obj):
3175 def displayhook(obj):
3129 if isinstance(obj, type) and issubclass(obj, Table):
3176 if isinstance(obj, type) and issubclass(obj, Table):
3130 obj = obj()
3177 obj = obj()
3131 if isinstance(obj, Table):
3178 if isinstance(obj, Table):
3132 obj = obj | defaultdisplay
3179 obj = obj | defaultdisplay
3133 if isinstance(obj, Display):
3180 if isinstance(obj, Display):
3134 return obj.display()
3181 return obj.display()
3135 else:
3182 else:
3136 _originalhook(obj)
3183 _originalhook(obj)
3137 sys.displayhook = displayhook
3184 sys.displayhook = displayhook
3138 installdisplayhook()
3185 installdisplayhook()
General Comments 0
You need to be logged in to leave comments. Login now