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