##// END OF EJS Templates
Walter's ipipe patch:...
vivainio -
Show More
@@ -1,2183 +1,2384 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 iwalk | ifilter("name.endswith('.py')") | isort("size")
11 >>> ienv | isort("_.key.lower()")
12
13 This gives a listing of all environment variables sorted by name.
12
14
13 This gives a listing of all files in the current directory (and subdirectories)
14 whose name ends with '.py' sorted by size.
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
35
36 Adding support for pipeline expressions to your own objects can be done through
37 three extensions points (all of them optional):
38
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
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
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
45 the attribute sequence (i.e. the object itself (it's ``repr()`` format) will
46 be being displayed. The global function ``xattrs()`` implements this
47 functionality.
48
49 * When an object ``foo`` is displayed in the header (or footer) of the browser
50 ``foo.__xrepr__("header")`` (or ``foo.__xrepr__("footer")``) is called.
51 Currently only ``"header"`` and ``"footer"`` are used as the mode argument.
52 When the method doesn't recognize the mode argument, it should fall back to
53 the ``repr()`` output. If the method ``__xrepr__()`` isn't defined the browser
54 falls back to ``repr()``. The global function ``xrepr()`` implements this
55 functionality.
56
57 * Objects that can be iterated by ``Pipe``s must implement the method
58 ``__xiter__(self, mode)``. ``mode`` can take the following values:
59
60 - ``"default"``: This is the default value and ist always used by pipeline
61 expressions. Other values are only used in the browser.
62 - ``None``: This value is passed by the browser. The object must return an
63 iterable of ``XMode`` objects describing all modes supported by the object.
64 (This should never include ``"default"`` or ``None``).
65 - Any other value that the object supports.
66
67 The global function ``xiter()`` can be called to get such an iterator. If
68 the method ``_xiter__`` isn't implemented, ``xiter()`` falls back to
69 ``__iter__``. In addition to that, dictionaries and modules receive special
70 treatment (returning an iterator over ``(key, value)`` pairs). This makes it
71 possible to use dictionaries and modules in pipeline expressions, for example:
72
73 >>> import sys
74 >>> sys | ifilter("isinstance(_.value, int)") | idump
75 key |value
76 api_version| 1012
77 dllhandle | 503316480
78 hexversion | 33817328
79 maxint |2147483647
80 maxunicode | 65535
81 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
82 ...
83
84 Note: The expression strings passed to ``ifilter()`` and ``isort()`` can
85 refer to the object to be filtered or sorted via the variable ``_``. When
86 running under Python 2.4 it's also possible to refer to the attributes of
87 the object directy, i.e.:
88
89 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
90
91 can be replaced with
92
93 >>> sys.modules | ifilter("value is not None") | isort("key.lower()")
94
95 In addition to expression strings, it's possible to pass callables (taking
96 the object as an argument) to ``ifilter()``, ``isort()`` and ``ieval()``:
97
98 >>> sys | ifilter(lambda _:isinstance(_.value, int)) \
99 ... | ieval(lambda _: (_.key, hex(_.value))) | idump
100 0 |1
101 api_version|0x3f4
102 dllhandle |0x1e000000
103 hexversion |0x20402f0
104 maxint |0x7fffffff
105 maxunicode |0xffff
34 """
106 """
35
107
36 import sys, os, os.path, stat, glob, new, csv, datetime
108 import sys, os, os.path, stat, glob, new, csv, datetime
37 import textwrap, itertools, mimetypes
109 import textwrap, itertools, mimetypes
38
110
39 try: # Python 2.3 compatibility
111 try: # Python 2.3 compatibility
40 import collections
112 import collections
41 except ImportError:
113 except ImportError:
42 deque = list
114 deque = list
43 else:
115 else:
44 deque = collections.deque
116 deque = collections.deque
45
117
46 try: # Python 2.3 compatibility
118 try: # Python 2.3 compatibility
47 set
119 set
48 except NameError:
120 except NameError:
49 import sets
121 import sets
50 set = sets.Set
122 set = sets.Set
51
123
52 try: # Python 2.3 compatibility
124 try: # Python 2.3 compatibility
53 sorted
125 sorted
54 except NameError:
126 except NameError:
55 def sorted(iterator, key=None, reverse=False):
127 def sorted(iterator, key=None, reverse=False):
56 items = list(iterator)
128 items = list(iterator)
57 if key is not None:
129 if key is not None:
58 items.sort(lambda i1, i2: cmp(key(i1), key(i2)))
130 items.sort(lambda i1, i2: cmp(key(i1), key(i2)))
59 else:
131 else:
60 items.sort()
132 items.sort()
61 if reverse:
133 if reverse:
62 items.reverse()
134 items.reverse()
63 return items
135 return items
64
136
65 try:
137 try:
66 import pwd
138 import pwd
67 except ImportError:
139 except ImportError:
68 pwd = None
140 pwd = None
69
141
70 try:
142 try:
71 import grp
143 import grp
72 except ImportError:
144 except ImportError:
73 grp = None
145 grp = None
74
146
75 try:
147 try:
76 import curses
148 import curses
77 except ImportError:
149 except ImportError:
78 curses = None
150 curses = None
79
151
80
152
81 __all__ = [
153 __all__ = [
82 "ifile", "ils", "iglob", "iwalk", "ipwdentry", "ipwd", "igrpentry", "igrp",
154 "ifile", "ils", "iglob", "iwalk", "ipwdentry", "ipwd", "igrpentry", "igrp",
83 "icsv", "ix", "ichain", "isort", "ifilter", "ieval", "ienum", "ienv",
155 "icsv", "ix", "ichain", "isort", "ifilter", "ieval", "ienum", "ienv",
84 "idump", "iless"
156 "idump", "iless"
85 ]
157 ]
86
158
87
159
88 os.stat_float_times(True) # enable microseconds
160 os.stat_float_times(True) # enable microseconds
89
161
90
162
163 class _AttrNamespace(object):
164 """
165 Internal helper class that is used for providing a namespace for evaluating
166 expressions containg attribute names of an object.
167
168 This class can only be used with Python 2.4.
169 """
170 def __init__(self, wrapped):
171 self.wrapped = wrapped
172
173 def __getitem__(self, name):
174 if name == "_":
175 return self.wrapped
176 try:
177 return getattr(self.wrapped, name)
178 except AttributeError:
179 raise KeyError(name)
180
181 # Python 2.3 compatibility
182 # fall back to a real dictionary (containing just the object itself) if we can't
183 # use the _AttrNamespace class in eval()
184 try:
185 eval("_", None, _AttrNamespace(None))
186 except TypeError:
187 def _AttrNamespace(wrapped):
188 return {"_": wrapped}
189
190
91 _default = object()
191 _default = object()
92
192
93 def item(iterator, index, default=_default):
193 def item(iterator, index, default=_default):
94 """
194 """
95 Return the ``index``th item from the iterator ``iterator``.
195 Return the ``index``th item from the iterator ``iterator``.
96 ``index`` must be an integer (negative integers are relative to the
196 ``index`` must be an integer (negative integers are relative to the
97 end (i.e. the last item produced by the iterator)).
197 end (i.e. the last item produced by the iterator)).
98
198
99 If ``default`` is given, this will be the default value when
199 If ``default`` is given, this will be the default value when
100 the iterator doesn't contain an item at this position. Otherwise an
200 the iterator doesn't contain an item at this position. Otherwise an
101 ``IndexError`` will be raised.
201 ``IndexError`` will be raised.
102
202
103 Note that using this function will partially or totally exhaust the
203 Note that using this function will partially or totally exhaust the
104 iterator.
204 iterator.
105 """
205 """
106 i = index
206 i = index
107 if i>=0:
207 if i>=0:
108 for item in iterator:
208 for item in iterator:
109 if not i:
209 if not i:
110 return item
210 return item
111 i -= 1
211 i -= 1
112 else:
212 else:
113 i = -index
213 i = -index
114 cache = deque()
214 cache = deque()
115 for item in iterator:
215 for item in iterator:
116 cache.append(item)
216 cache.append(item)
117 if len(cache)>i:
217 if len(cache)>i:
118 cache.popleft()
218 cache.popleft()
119 if len(cache)==i:
219 if len(cache)==i:
120 return cache.popleft()
220 return cache.popleft()
121 if default is _default:
221 if default is _default:
122 raise IndexError(index)
222 raise IndexError(index)
123 else:
223 else:
124 return default
224 return default
125
225
126
226
127 class _AttrNamespace(object):
128 """
129 Internal helper that is used for providing a namespace for evaluating
130 expressions containg attribute names of an object.
131 """
132 def __init__(self, wrapped):
133 self.wrapped = wrapped
134
135 def __getitem__(self, name):
136 if name == "_":
137 return self.wrapped
138 try:
139 return getattr(self.wrapped, name)
140 except AttributeError:
141 raise KeyError(name)
142
143
144 class Table(object):
227 class Table(object):
145 """
228 """
146 A ``Table`` is an object that produces items (just like a normal Python
229 A ``Table`` is an object that produces items (just like a normal Python
147 iterator/generator does) and can be used as the first object in a pipeline
230 iterator/generator does) and can be used as the first object in a pipeline
148 expression. The displayhook will open the default browser for such an object
231 expression. The displayhook will open the default browser for such an object
149 (instead of simply printing the ``repr()`` result).
232 (instead of simply printing the ``repr()`` result).
150 """
233 """
234
235 # We want to support ``foo`` and ``foo()`` in pipeline expression:
236 # So we implement the required operators (``|`` and ``+``) in the metaclass,
237 # instantiate the class and forward the operator to the instance
151 class __metaclass__(type):
238 class __metaclass__(type):
152 def __iter__(self):
239 def __iter__(self):
153 return iter(self())
240 return iter(self())
154
241
155 def __or__(self, other):
242 def __or__(self, other):
156 return self() | other
243 return self() | other
157
244
158 def __add__(self, other):
245 def __add__(self, other):
159 return self() + other
246 return self() + other
160
247
161 def __radd__(self, other):
248 def __radd__(self, other):
162 return other + self()
249 return other + self()
163
250
164 def __getitem__(self, index):
251 def __getitem__(self, index):
165 return self()[index]
252 return self()[index]
166
253
167 def __getitem__(self, index):
254 def __getitem__(self, index):
168 return item(self, index)
255 return item(self, index)
169
256
170 def __contains__(self, item):
257 def __contains__(self, item):
171 for haveitem in self:
258 for haveitem in self:
172 if item == haveitem:
259 if item == haveitem:
173 return True
260 return True
174 return False
261 return False
175
262
176 def __or__(self, other):
263 def __or__(self, other):
264 # autoinstantiate right hand side
177 if isinstance(other, type) and issubclass(other, (Table, Display)):
265 if isinstance(other, type) and issubclass(other, (Table, Display)):
178 other = other()
266 other = other()
267 # treat simple strings and functions as ``ieval`` instances
179 elif not isinstance(other, Display) and not isinstance(other, Table):
268 elif not isinstance(other, Display) and not isinstance(other, Table):
180 other = ieval(other)
269 other = ieval(other)
270 # forward operations to the right hand side
181 return other.__ror__(self)
271 return other.__ror__(self)
182
272
183 def __add__(self, other):
273 def __add__(self, other):
274 # autoinstantiate right hand side
184 if isinstance(other, type) and issubclass(other, Table):
275 if isinstance(other, type) and issubclass(other, Table):
185 other = other()
276 other = other()
186 return ichain(self, other)
277 return ichain(self, other)
187
278
188 def __radd__(self, other):
279 def __radd__(self, other):
280 # autoinstantiate left hand side
189 if isinstance(other, type) and issubclass(other, Table):
281 if isinstance(other, type) and issubclass(other, Table):
190 other = other()
282 other = other()
191 return ichain(other, self)
283 return ichain(other, self)
192
284
193 def __iter__(self):
285 def __iter__(self):
194 return xiter(self, "default")
286 return xiter(self, "default")
195
287
196
288
197 class Pipe(Table):
289 class Pipe(Table):
198 """
290 """
199 A ``Pipe`` is an object that can be used in a pipeline expression. It
291 A ``Pipe`` is an object that can be used in a pipeline expression. It
200 processes the objects it gets from its input ``Table``/``Pipe``. Note that
292 processes the objects it gets from its input ``Table``/``Pipe``. Note that
201 a ``Pipe`` object can't be used as the first object in a pipeline
293 a ``Pipe`` object can't be used as the first object in a pipeline
202 expression, as it doesn't produces items itself.
294 expression, as it doesn't produces items itself.
203 """
295 """
204 class __metaclass__(Table.__metaclass__):
296 class __metaclass__(Table.__metaclass__):
205 def __ror__(self, input):
297 def __ror__(self, input):
206 return input | self()
298 return input | self()
207
299
208 def __ror__(self, input):
300 def __ror__(self, input):
301 # autoinstantiate left hand side
209 if isinstance(input, type) and issubclass(input, Table):
302 if isinstance(input, type) and issubclass(input, Table):
210 input = input()
303 input = input()
211 self.input = input
304 self.input = input
212 return self
305 return self
213
306
214
307
215 def _getattr(obj, name, default=_default):
308 def _getattr(obj, name, default=_default):
216 """
309 """
217 Internal helper for getting an attribute of an item. If ``name`` is ``None``
310 Internal helper for getting an attribute of an item. If ``name`` is ``None``
218 return the object itself. If ``name`` is an integer, use ``__getitem__``
311 return the object itself. If ``name`` is an integer, use ``__getitem__``
219 instead. If the attribute or item does not exist, return ``default``.
312 instead. If the attribute or item does not exist, return ``default``.
220 """
313 """
221 if name is None:
314 if name is None:
222 return obj
315 return obj
223 elif isinstance(name, basestring):
316 elif isinstance(name, basestring):
224 return getattr(obj, name, default)
317 return getattr(obj, name, default)
225 elif callable(name):
318 elif callable(name):
226 return name(obj)
319 return name(obj)
227 else:
320 else:
228 try:
321 try:
229 return obj[name]
322 return obj[name]
230 except IndexError:
323 except IndexError:
231 return default
324 return default
232
325
233
326
234 def _attrname(name):
327 def _attrname(name):
235 """
328 """
236 Internal helper that gives a proper name for the attribute ``name``
329 Internal helper that gives a proper name for the attribute ``name``
237 (which might be ``None`` or an ``int``).
330 (which might be ``None`` or an ``int``).
238 """
331 """
239 if name is None:
332 if name is None:
240 return "_"
333 return "_"
241 elif isinstance(name, basestring):
334 elif isinstance(name, basestring):
242 return name
335 return name
243 elif callable(name):
336 elif callable(name):
244 return name.__name__
337 return name.__name__
245 else:
338 else:
246 return str(name)
339 return str(name)
247
340
248
341
249 def xrepr(item, mode):
342 def xrepr(item, mode):
250 try:
343 try:
251 func = item.__xrepr__
344 func = item.__xrepr__
252 except (KeyboardInterrupt, SystemExit):
345 except AttributeError:
253 raise
254 except Exception:
255 return repr(item)
346 return repr(item)
256 else:
347 else:
257 return func(mode)
348 return func(mode)
258
349
259
350
260 def xattrs(item, mode):
351 def xattrs(item, mode):
261 try:
352 try:
262 func = item.__xattrs__
353 func = item.__xattrs__
263 except AttributeError:
354 except AttributeError:
264 if isinstance(item, (list, tuple)):
355 if isinstance(item, (list, tuple)):
265 return xrange(len(item))
356 return xrange(len(item))
266 return (None,)
357 return (None,)
267 else:
358 else:
268 return func(mode)
359 return func(mode)
269
360
270
361
271 def xiter(item, mode):
362 def xiter(item, mode):
272 if mode == "detail":
363 if mode == "detail":
273 def items():
364 def items():
274 for name in xattrs(item, mode):
365 for name in xattrs(item, mode):
275 yield XAttr(item, name)
366 yield XAttr(item, name)
276 return items()
367 return items()
277 try:
368 try:
278 func = item.__xiter__
369 func = item.__xiter__
279 except AttributeError:
370 except AttributeError:
280 if isinstance(item, dict):
371 if isinstance(item, dict):
281 def items(item):
372 def items(item):
282 fields = ("key", "value")
373 fields = ("key", "value")
283 for (key, value) in item.iteritems():
374 for (key, value) in item.iteritems():
284 yield Fields(fields, key=key, value=value)
375 yield Fields(fields, key=key, value=value)
285 return items(item)
376 return items(item)
286 elif isinstance(item, new.module):
377 elif isinstance(item, new.module):
287 def items(item):
378 def items(item):
288 fields = ("key", "value")
379 fields = ("key", "value")
289 for key in sorted(item.__dict__):
380 for key in sorted(item.__dict__):
290 yield Fields(fields, key, getattr(item, key))
381 yield Fields(fields, key=key, value=getattr(item, key))
291 return items(item)
382 return items(item)
292 elif isinstance(item, basestring):
383 elif isinstance(item, basestring):
293 if not len(item):
384 if not len(item):
294 raise ValueError("can't enter empty string")
385 raise ValueError("can't enter empty string")
295 lines = item.splitlines()
386 lines = item.splitlines()
296 if len(lines) <= 1:
387 if len(lines) <= 1:
297 raise ValueError("can't enter one line string")
388 raise ValueError("can't enter one line string")
298 return iter(lines)
389 return iter(lines)
299 return iter(item)
390 return iter(item)
300 else:
391 else:
301 return iter(func(mode)) # iter() just to be safe
392 return iter(func(mode)) # iter() just to be safe
302
393
303
394
304 class ichain(Pipe):
395 class ichain(Pipe):
305 """
396 """
306 Chains multiple ``Table``s into one.
397 Chains multiple ``Table``s into one.
307 """
398 """
308
399
309 def __init__(self, *iters):
400 def __init__(self, *iters):
310 self.iters = iters
401 self.iters = iters
311
402
312 def __xiter__(self, mode):
403 def __xiter__(self, mode):
313 return itertools.chain(*self.iters)
404 return itertools.chain(*self.iters)
314
405
315 def __xrepr__(self, mode):
406 def __xrepr__(self, mode):
316 if mode == "header" or mode == "footer":
407 if mode == "header" or mode == "footer":
317 parts = []
408 parts = []
318 for item in self.iters:
409 for item in self.iters:
319 part = xrepr(item, mode)
410 part = xrepr(item, mode)
320 if isinstance(item, Pipe):
411 if isinstance(item, Pipe):
321 part = "(%s)" % part
412 part = "(%s)" % part
322 parts.append(part)
413 parts.append(part)
323 return "+".join(parts)
414 return "+".join(parts)
324 return repr(self)
415 return repr(self)
325
416
326 def __repr__(self):
417 def __repr__(self):
327 args = ", ".join([repr(it) for it in self.iters])
418 args = ", ".join([repr(it) for it in self.iters])
328 return "%s.%s(%s)" % \
419 return "%s.%s(%s)" % \
329 (self.__class__.__module__, self.__class__.__name__, args)
420 (self.__class__.__module__, self.__class__.__name__, args)
330
421
331
422
332 class ifile(object):
423 class ifile(object):
333 __slots__ = ("name", "_abspath", "_realpath", "_stat", "_lstat")
424 __slots__ = ("name", "_abspath", "_realpath", "_stat", "_lstat")
334
425
335 def __init__(self, name):
426 def __init__(self, name):
336 if isinstance(name, ifile): # copying files
427 if isinstance(name, ifile): # copying files
337 self.name = name.name
428 self.name = name.name
338 self._abspath = name._abspath
429 self._abspath = name._abspath
339 self._realpath = name._realpath
430 self._realpath = name._realpath
340 self._stat = name._stat
431 self._stat = name._stat
341 self._lstat = name._lstat
432 self._lstat = name._lstat
342 else:
433 else:
343 self.name = os.path.normpath(name)
434 self.name = os.path.normpath(name)
344 self._abspath = None
435 self._abspath = None
345 self._realpath = None
436 self._realpath = None
346 self._stat = None
437 self._stat = None
347 self._lstat = None
438 self._lstat = None
348
439
349 def __repr__(self):
440 def __repr__(self):
350 return "%s.%s(%r)" % \
441 return "%s.%s(%r)" % \
351 (self.__class__.__module__, self.__class__.__name__, self.name)
442 (self.__class__.__module__, self.__class__.__name__, self.name)
352
443
353 def open(self, mode="rb", buffer=None):
444 def open(self, mode="rb", buffer=None):
354 if buffer is None:
445 if buffer is None:
355 return open(self.abspath, mode)
446 return open(self.abspath, mode)
356 else:
447 else:
357 return open(self.abspath, mode, buffer)
448 return open(self.abspath, mode, buffer)
358
449
359 def remove(self):
450 def remove(self):
360 os.remove(self.abspath)
451 os.remove(self.abspath)
361
452
362 def getabspath(self):
453 def getabspath(self):
363 if self._abspath is None:
454 if self._abspath is None:
364 self._abspath = os.path.abspath(self.name)
455 self._abspath = os.path.abspath(self.name)
365 return self._abspath
456 return self._abspath
366 abspath = property(getabspath, None, None, "Path to file")
457 abspath = property(getabspath, None, None, "Path to file")
367
458
368 def getrealpath(self):
459 def getrealpath(self):
369 if self._realpath is None:
460 if self._realpath is None:
370 self._realpath = os.path.realpath(self.name)
461 self._realpath = os.path.realpath(self.name)
371 return self._realpath
462 return self._realpath
372 realpath = property(getrealpath, None, None, "Path with links resolved")
463 realpath = property(getrealpath, None, None, "Path with links resolved")
373
464
374 def getbasename(self):
465 def getbasename(self):
375 return os.path.basename(self.abspath)
466 return os.path.basename(self.abspath)
376 basename = property(getbasename, None, None, "File name without directory")
467 basename = property(getbasename, None, None, "File name without directory")
377
468
378 def getstat(self):
469 def getstat(self):
379 if self._stat is None:
470 if self._stat is None:
380 self._stat = os.stat(self.abspath)
471 self._stat = os.stat(self.abspath)
381 return self._stat
472 return self._stat
382 stat = property(getstat, None, None, "os.stat() result")
473 stat = property(getstat, None, None, "os.stat() result")
383
474
384 def getlstat(self):
475 def getlstat(self):
385 if self._lstat is None:
476 if self._lstat is None:
386 self._lstat = os.lstat(self.abspath)
477 self._lstat = os.lstat(self.abspath)
387 return self._lstat
478 return self._lstat
388 lstat = property(getlstat, None, None, "os.lstat() result")
479 lstat = property(getlstat, None, None, "os.lstat() result")
389
480
390 def getmode(self):
481 def getmode(self):
391 return self.stat.st_mode
482 return self.stat.st_mode
392 mode = property(getmode, None, None, "Access mode")
483 mode = property(getmode, None, None, "Access mode")
393
484
394 def gettype(self):
485 def gettype(self):
395 data = [
486 data = [
396 (stat.S_ISREG, "file"),
487 (stat.S_ISREG, "file"),
397 (stat.S_ISDIR, "dir"),
488 (stat.S_ISDIR, "dir"),
398 (stat.S_ISCHR, "chardev"),
489 (stat.S_ISCHR, "chardev"),
399 (stat.S_ISBLK, "blockdev"),
490 (stat.S_ISBLK, "blockdev"),
400 (stat.S_ISFIFO, "fifo"),
491 (stat.S_ISFIFO, "fifo"),
401 (stat.S_ISLNK, "symlink"),
492 (stat.S_ISLNK, "symlink"),
402 (stat.S_ISSOCK,"socket"),
493 (stat.S_ISSOCK,"socket"),
403 ]
494 ]
404 lstat = self.lstat
495 lstat = self.lstat
405 if lstat is not None:
496 if lstat is not None:
406 types = set([text for (func, text) in data if func(lstat.st_mode)])
497 types = set([text for (func, text) in data if func(lstat.st_mode)])
407 else:
498 else:
408 types = set()
499 types = set()
409 m = self.mode
500 m = self.mode
410 types.update([text for (func, text) in data if func(m)])
501 types.update([text for (func, text) in data if func(m)])
411 return ", ".join(types)
502 return ", ".join(types)
412 type = property(gettype, None, None, "file type")
503 type = property(gettype, None, None, "file type")
413
504
414 def getaccess(self):
505 def getaccess(self):
415 m = self.mode
506 m = self.mode
416 data = [
507 data = [
417 (stat.S_IRUSR, "-r"),
508 (stat.S_IRUSR, "-r"),
418 (stat.S_IWUSR, "-w"),
509 (stat.S_IWUSR, "-w"),
419 (stat.S_IXUSR, "-x"),
510 (stat.S_IXUSR, "-x"),
420 (stat.S_IRGRP, "-r"),
511 (stat.S_IRGRP, "-r"),
421 (stat.S_IWGRP, "-w"),
512 (stat.S_IWGRP, "-w"),
422 (stat.S_IXGRP, "-x"),
513 (stat.S_IXGRP, "-x"),
423 (stat.S_IROTH, "-r"),
514 (stat.S_IROTH, "-r"),
424 (stat.S_IWOTH, "-w"),
515 (stat.S_IWOTH, "-w"),
425 (stat.S_IXOTH, "-x"),
516 (stat.S_IXOTH, "-x"),
426 ]
517 ]
427 return "".join([text[bool(m&bit)] for (bit, text) in data])
518 return "".join([text[bool(m&bit)] for (bit, text) in data])
428
519
429 access = property(getaccess, None, None, "Access mode as string")
520 access = property(getaccess, None, None, "Access mode as string")
430
521
431 def getsize(self):
522 def getsize(self):
432 return int(self.stat.st_size)
523 return int(self.stat.st_size)
433 size = property(getsize, None, None, "File size in bytes")
524 size = property(getsize, None, None, "File size in bytes")
434
525
435 def getblocks(self):
526 def getblocks(self):
436 return self.stat.st_blocks
527 return self.stat.st_blocks
437 blocks = property(getblocks, None, None, "File size in blocks")
528 blocks = property(getblocks, None, None, "File size in blocks")
438
529
439 def getblksize(self):
530 def getblksize(self):
440 return self.stat.st_blksize
531 return self.stat.st_blksize
441 blksize = property(getblksize, None, None, "Filesystem block size")
532 blksize = property(getblksize, None, None, "Filesystem block size")
442
533
443 def getdev(self):
534 def getdev(self):
444 return self.stat.st_dev
535 return self.stat.st_dev
445 dev = property(getdev)
536 dev = property(getdev)
446
537
447 def getnlink(self):
538 def getnlink(self):
448 return self.stat.st_nlink
539 return self.stat.st_nlink
449 nlink = property(getnlink, None, None, "Number of links")
540 nlink = property(getnlink, None, None, "Number of links")
450
541
451 def getuid(self):
542 def getuid(self):
452 return self.stat.st_uid
543 return self.stat.st_uid
453 uid = property(getuid, None, None, "User id of file owner")
544 uid = property(getuid, None, None, "User id of file owner")
454
545
455 def getgid(self):
546 def getgid(self):
456 return self.stat.st_gid
547 return self.stat.st_gid
457 gid = property(getgid, None, None, "Group id of file owner")
548 gid = property(getgid, None, None, "Group id of file owner")
458
549
459 def getowner(self):
550 def getowner(self):
460 try:
551 try:
461 return pwd.getpwuid(self.stat.st_uid).pw_name
552 return pwd.getpwuid(self.stat.st_uid).pw_name
462 except KeyError:
553 except KeyError:
463 return self.stat.st_uid
554 return self.stat.st_uid
464 owner = property(getowner, None, None, "Owner name (or id)")
555 owner = property(getowner, None, None, "Owner name (or id)")
465
556
466 def getgroup(self):
557 def getgroup(self):
467 try:
558 try:
468 return grp.getgrgid(self.stat.st_gid).gr_name
559 return grp.getgrgid(self.stat.st_gid).gr_name
469 except KeyError:
560 except KeyError:
470 return self.stat.st_gid
561 return self.stat.st_gid
471 group = property(getgroup, None, None, "Group name (or id)")
562 group = property(getgroup, None, None, "Group name (or id)")
472
563
473 def getatime(self):
564 def getatime(self):
474 return self.stat.st_atime
565 return self.stat.st_atime
475 atime = property(getatime, None, None, "Access date")
566 atime = property(getatime, None, None, "Access date")
476
567
477 def getadate(self):
568 def getadate(self):
478 return datetime.datetime.utcfromtimestamp(self.atime)
569 return datetime.datetime.utcfromtimestamp(self.atime)
479 adate = property(getadate, None, None, "Access date")
570 adate = property(getadate, None, None, "Access date")
480
571
481 def getctime(self):
572 def getctime(self):
482 return self.stat.st_ctime
573 return self.stat.st_ctime
483 ctime = property(getctime, None, None, "Creation date")
574 ctime = property(getctime, None, None, "Creation date")
484
575
485 def getcdate(self):
576 def getcdate(self):
486 return datetime.datetime.utcfromtimestamp(self.ctime)
577 return datetime.datetime.utcfromtimestamp(self.ctime)
487 cdate = property(getcdate, None, None, "Creation date")
578 cdate = property(getcdate, None, None, "Creation date")
488
579
489 def getmtime(self):
580 def getmtime(self):
490 return self.stat.st_mtime
581 return self.stat.st_mtime
491 mtime = property(getmtime, None, None, "Modification date")
582 mtime = property(getmtime, None, None, "Modification date")
492
583
493 def getmdate(self):
584 def getmdate(self):
494 return datetime.datetime.utcfromtimestamp(self.mtime)
585 return datetime.datetime.utcfromtimestamp(self.mtime)
495 mdate = property(getmdate, None, None, "Modification date")
586 mdate = property(getmdate, None, None, "Modification date")
496
587
497 def getmimetype(self):
588 def getmimetype(self):
498 return mimetypes.guess_type(self.basename)[0]
589 return mimetypes.guess_type(self.basename)[0]
499 mimetype = property(getmimetype, None, None, "MIME type")
590 mimetype = property(getmimetype, None, None, "MIME type")
500
591
501 def getencoding(self):
592 def getencoding(self):
502 return mimetypes.guess_type(self.basename)[1]
593 return mimetypes.guess_type(self.basename)[1]
503 encoding = property(getencoding, None, None, "Compression")
594 encoding = property(getencoding, None, None, "Compression")
504
595
505 def getisdir(self):
596 def getisdir(self):
506 return os.path.isdir(self.abspath)
597 return os.path.isdir(self.abspath)
507 isdir = property(getisdir, None, None, "Is this a directory?")
598 isdir = property(getisdir, None, None, "Is this a directory?")
508
599
509 def getislink(self):
600 def getislink(self):
510 return os.path.islink(self.abspath)
601 return os.path.islink(self.abspath)
511 islink = property(getislink, None, None, "Is this a link?")
602 islink = property(getislink, None, None, "Is this a link?")
512
603
513 def __eq__(self, other):
604 def __eq__(self, other):
514 return self.abspath == other.abspath
605 return self.abspath == other.abspath
515
606
516 def __neq__(self, other):
607 def __neq__(self, other):
517 return self.abspath != other.abspath
608 return self.abspath != other.abspath
518
609
519 def __xattrs__(self, mode):
610 def __xattrs__(self, mode):
520 if mode == "detail":
611 if mode == "detail":
521 return (
612 return (
522 "name", "basename", "abspath", "realpath",
613 "name", "basename", "abspath", "realpath",
523 "mode", "type", "access", "stat", "lstat",
614 "mode", "type", "access", "stat", "lstat",
524 "uid", "gid", "owner", "group", "dev", "nlink",
615 "uid", "gid", "owner", "group", "dev", "nlink",
525 "ctime", "mtime", "atime", "cdate", "mdate", "adate",
616 "ctime", "mtime", "atime", "cdate", "mdate", "adate",
526 "size", "blocks", "blksize", "isdir", "islink",
617 "size", "blocks", "blksize", "isdir", "islink",
527 "mimetype", "encoding"
618 "mimetype", "encoding"
528 )
619 )
529 return ("name","type", "size", "access", "owner", "group", "mdate")
620 return ("name", "type", "size", "access", "owner", "group", "mdate")
530
621
531 def __xrepr__(self, mode):
622 def __xrepr__(self, mode):
532 if mode == "header" or mode == "footer":
623 if mode == "header" or mode == "footer":
533 name = "ifile"
624 name = "ifile"
534 try:
625 try:
535 if self.isdir:
626 if self.isdir:
536 name = "idir"
627 name = "idir"
537 except IOError:
628 except IOError:
538 pass
629 pass
539 return "%s(%r)" % (name, self.abspath)
630 return "%s(%r)" % (name, self.abspath)
540 return repr(self)
631 return repr(self)
541
632
542 def __xiter__(self, mode):
633 def __xiter__(self, mode):
543 if self.isdir:
634 if self.isdir:
544 abspath = self.abspath
635 abspath = self.abspath
545 if abspath != os.path.abspath(os.path.join(abspath, os.pardir)):
636 if abspath != os.path.abspath(os.path.join(abspath, os.pardir)):
546 yield iparentdir(abspath)
637 yield iparentdir(abspath)
547 for name in sorted(os.listdir(abspath), key=lambda n: n.lower()):
638 for name in sorted(os.listdir(abspath), key=lambda n: n.lower()):
548 if self.name != os.curdir:
639 if self.name != os.curdir:
549 name = os.path.join(abspath, name)
640 name = os.path.join(abspath, name)
550 yield ifile(name)
641 yield ifile(name)
551 else:
642 else:
552 f = self.open("rb")
643 f = self.open("rb")
553 for line in f:
644 for line in f:
554 yield line
645 yield line
555 f.close()
646 f.close()
556
647
557 def __repr__(self):
648 def __repr__(self):
558 return "%s.%s(%r)" % \
649 return "%s.%s(%r)" % \
559 (self.__class__.__module__, self.__class__.__name__, self.abspath)
650 (self.__class__.__module__, self.__class__.__name__, self.abspath)
560
651
561
652
562 class iparentdir(ifile):
653 class iparentdir(ifile):
563 def __init__(self, base):
654 def __init__(self, base):
564 self._base = base
655 self._base = base
565 self.name = os.pardir
656 self.name = os.pardir
566 self._abspath = None
657 self._abspath = None
567 self._realpath = None
658 self._realpath = None
568 self._stat = None
659 self._stat = None
569 self._lstat = None
660 self._lstat = None
570
661
571 def getabspath(self):
662 def getabspath(self):
572 if self._abspath is None:
663 if self._abspath is None:
573 self._abspath = os.path.abspath(os.path.join(self._base, self.name))
664 self._abspath = os.path.abspath(os.path.join(self._base, self.name))
574 return self._abspath
665 return self._abspath
575 abspath = property(getabspath, None, None, "Path to file")
666 abspath = property(getabspath, None, None, "Path to file")
576
667
577 def getrealpath(self):
668 def getrealpath(self):
578 if self._realpath is None:
669 if self._realpath is None:
579 self._realpath = os.path.realpath(
670 self._realpath = os.path.realpath(
580 os.path.join(self._base, self.name))
671 os.path.join(self._base, self.name))
581 return self._realpath
672 return self._realpath
582 realpath = property(getrealpath, None, None, "Path with links resolved")
673 realpath = property(getrealpath, None, None, "Path with links resolved")
583
674
584
675
585 class ils(Table):
676 class ils(Table):
586 def __init__(self, base=os.curdir):
677 def __init__(self, base=os.curdir):
587 self.base = os.path.expanduser(base)
678 self.base = os.path.expanduser(base)
588
679
589 def __xiter__(self, mode):
680 def __xiter__(self, mode):
590 return xiter(ifile(self.base), mode)
681 return xiter(ifile(self.base), mode)
591
682
592 def __xrepr__(self, mode):
683 def __xrepr__(self, mode):
593 if mode == "header" or mode == "footer":
684 if mode == "header" or mode == "footer":
594 return "idir(%r)" % (os.path.abspath(self.base))
685 return "idir(%r)" % (os.path.abspath(self.base))
595 return repr(self)
686 return repr(self)
596
687
597 def __repr__(self):
688 def __repr__(self):
598 return "%s.%s(%r)" % \
689 return "%s.%s(%r)" % \
599 (self.__class__.__module__, self.__class__.__name__, self.base)
690 (self.__class__.__module__, self.__class__.__name__, self.base)
600
691
601
692
602 class iglob(Table):
693 class iglob(Table):
603 def __init__(self, glob):
694 def __init__(self, glob):
604 self.glob = glob
695 self.glob = glob
605
696
606 def __xiter__(self, mode):
697 def __xiter__(self, mode):
607 for name in glob.glob(self.glob):
698 for name in glob.glob(self.glob):
608 yield ifile(name)
699 yield ifile(name)
609
700
610 def __xrepr__(self, mode):
701 def __xrepr__(self, mode):
611 if mode == "header" or mode == "footer":
702 if mode == "header" or mode == "footer":
612 return "%s(%r)" % (self.__class__.__name__, self.glob)
703 return "%s(%r)" % (self.__class__.__name__, self.glob)
613 return repr(self)
704 return repr(self)
614
705
615 def __repr__(self):
706 def __repr__(self):
616 return "%s.%s(%r)" % \
707 return "%s.%s(%r)" % \
617 (self.__class__.__module__, self.__class__.__name__, self.glob)
708 (self.__class__.__module__, self.__class__.__name__, self.glob)
618
709
619
710
620 class iwalk(Table):
711 class iwalk(Table):
621 def __init__(self, base=os.curdir, dirs=True, files=True):
712 def __init__(self, base=os.curdir, dirs=True, files=True):
622 self.base = os.path.expanduser(base)
713 self.base = os.path.expanduser(base)
623 self.dirs = dirs
714 self.dirs = dirs
624 self.files = files
715 self.files = files
625
716
626 def __xiter__(self, mode):
717 def __xiter__(self, mode):
627 for (dirpath, dirnames, filenames) in os.walk(self.base):
718 for (dirpath, dirnames, filenames) in os.walk(self.base):
628 if self.dirs:
719 if self.dirs:
629 for name in sorted(dirnames):
720 for name in sorted(dirnames):
630 yield ifile(os.path.join(dirpath, name))
721 yield ifile(os.path.join(dirpath, name))
631 if self.files:
722 if self.files:
632 for name in sorted(filenames):
723 for name in sorted(filenames):
633 yield ifile(os.path.join(dirpath, name))
724 yield ifile(os.path.join(dirpath, name))
634
725
635 def __xrepr__(self, mode):
726 def __xrepr__(self, mode):
636 if mode == "header" or mode == "footer":
727 if mode == "header" or mode == "footer":
637 return "%s(%r)" % (self.__class__.__name__, self.base)
728 return "%s(%r)" % (self.__class__.__name__, self.base)
638 return repr(self)
729 return repr(self)
639
730
640 def __repr__(self):
731 def __repr__(self):
641 return "%s.%s(%r)" % \
732 return "%s.%s(%r)" % \
642 (self.__class__.__module__, self.__class__.__name__, self.base)
733 (self.__class__.__module__, self.__class__.__name__, self.base)
643
734
644
735
645 class ipwdentry(object):
736 class ipwdentry(object):
646 def __init__(self, id):
737 def __init__(self, id):
647 self._id = id
738 self._id = id
648 self._entry = None
739 self._entry = None
649
740
650 def _getentry(self):
741 def _getentry(self):
651 if self._entry is None:
742 if self._entry is None:
652 if isinstance(self._id, basestring):
743 if isinstance(self._id, basestring):
653 self._entry = pwd.getpwnam(self._id)
744 self._entry = pwd.getpwnam(self._id)
654 else:
745 else:
655 self._entry = pwd.getpwuid(self._id)
746 self._entry = pwd.getpwuid(self._id)
656 return self._entry
747 return self._entry
657
748
658 def getname(self):
749 def getname(self):
659 if isinstance(self._id, basestring):
750 if isinstance(self._id, basestring):
660 return self._id
751 return self._id
661 else:
752 else:
662 return self._getentry().pw_name
753 return self._getentry().pw_name
663 name = property(getname, None, None, "User name")
754 name = property(getname, None, None, "User name")
664
755
665 def getpasswd(self):
756 def getpasswd(self):
666 return self._getentry().pw_passwd
757 return self._getentry().pw_passwd
667 passwd = property(getpasswd, None, None, "Password")
758 passwd = property(getpasswd, None, None, "Password")
668
759
669 def getuid(self):
760 def getuid(self):
670 if isinstance(self._id, basestring):
761 if isinstance(self._id, basestring):
671 return self._getentry().pw_uid
762 return self._getentry().pw_uid
672 else:
763 else:
673 return self._id
764 return self._id
674 uid = property(getuid, None, None, "User id")
765 uid = property(getuid, None, None, "User id")
675
766
676 def getgid(self):
767 def getgid(self):
677 return self._getentry().pw_gid
768 return self._getentry().pw_gid
678 gid = property(getgid, None, None, "Primary group id")
769 gid = property(getgid, None, None, "Primary group id")
679
770
680 def getgroup(self):
771 def getgroup(self):
681 return igrpentry(self.gid)
772 return igrpentry(self.gid)
682 group = property(getgroup, None, None, "Group")
773 group = property(getgroup, None, None, "Group")
683
774
684 def getgecos(self):
775 def getgecos(self):
685 return self._getentry().pw_gecos
776 return self._getentry().pw_gecos
686 gecos = property(getgecos, None, None, "Information (e.g. full user name)")
777 gecos = property(getgecos, None, None, "Information (e.g. full user name)")
687
778
688 def getdir(self):
779 def getdir(self):
689 return self._getentry().pw_dir
780 return self._getentry().pw_dir
690 dir = property(getdir, None, None, "$HOME directory")
781 dir = property(getdir, None, None, "$HOME directory")
691
782
692 def getshell(self):
783 def getshell(self):
693 return self._getentry().pw_shell
784 return self._getentry().pw_shell
694 shell = property(getshell, None, None, "Login shell")
785 shell = property(getshell, None, None, "Login shell")
695
786
696 def __xattrs__(self, mode):
787 def __xattrs__(self, mode):
697 return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell")
788 return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell")
698
789
699 def __repr__(self):
790 def __repr__(self):
700 return "%s.%s(%r)" % \
791 return "%s.%s(%r)" % \
701 (self.__class__.__module__, self.__class__.__name__, self._id)
792 (self.__class__.__module__, self.__class__.__name__, self._id)
702
793
703
794
704 class ipwd(Table):
795 class ipwd(Table):
705 def __iter__(self):
796 def __iter__(self):
706 for entry in pwd.getpwall():
797 for entry in pwd.getpwall():
707 yield ipwdentry(entry.pw_name)
798 yield ipwdentry(entry.pw_name)
708
799
709 def __xrepr__(self, mode):
800 def __xrepr__(self, mode):
710 if mode == "header" or mode == "footer":
801 if mode == "header" or mode == "footer":
711 return "%s()" % self.__class__.__name__
802 return "%s()" % self.__class__.__name__
712 return repr(self)
803 return repr(self)
713
804
714
805
715 class igrpentry(object):
806 class igrpentry(object):
716 def __init__(self, id):
807 def __init__(self, id):
717 self._id = id
808 self._id = id
718 self._entry = None
809 self._entry = None
719
810
720 def _getentry(self):
811 def _getentry(self):
721 if self._entry is None:
812 if self._entry is None:
722 if isinstance(self._id, basestring):
813 if isinstance(self._id, basestring):
723 self._entry = grp.getgrnam(self._id)
814 self._entry = grp.getgrnam(self._id)
724 else:
815 else:
725 self._entry = grp.getgrgid(self._id)
816 self._entry = grp.getgrgid(self._id)
726 return self._entry
817 return self._entry
727
818
728 def getname(self):
819 def getname(self):
729 if isinstance(self._id, basestring):
820 if isinstance(self._id, basestring):
730 return self._id
821 return self._id
731 else:
822 else:
732 return self._getentry().gr_name
823 return self._getentry().gr_name
733 name = property(getname, None, None, "Group name")
824 name = property(getname, None, None, "Group name")
734
825
735 def getpasswd(self):
826 def getpasswd(self):
736 return self._getentry().gr_passwd
827 return self._getentry().gr_passwd
737 passwd = property(getpasswd, None, None, "Password")
828 passwd = property(getpasswd, None, None, "Password")
738
829
739 def getgid(self):
830 def getgid(self):
740 if isinstance(self._id, basestring):
831 if isinstance(self._id, basestring):
741 return self._getentry().gr_gid
832 return self._getentry().gr_gid
742 else:
833 else:
743 return self._id
834 return self._id
744 gid = property(getgid, None, None, "Group id")
835 gid = property(getgid, None, None, "Group id")
745
836
746 def getmem(self):
837 def getmem(self):
747 return self._getentry().gr_mem
838 return self._getentry().gr_mem
748 mem = property(getmem, None, None, "Members")
839 mem = property(getmem, None, None, "Members")
749
840
750 def __xattrs__(self, mode):
841 def __xattrs__(self, mode):
751 return ("name", "passwd", "gid", "mem")
842 return ("name", "passwd", "gid", "mem")
752
843
753 def __xrepr__(self, mode):
844 def __xrepr__(self, mode):
754 if mode == "header" or mode == "footer":
845 if mode == "header" or mode == "footer":
755 return "group %s" % self.name
846 return "group %s" % self.name
756 return repr(self)
847 return repr(self)
757
848
758 def __xiter__(self, mode):
849 def __xiter__(self, mode):
759 for member in self.mem:
850 for member in self.mem:
760 yield ipwdentry(member)
851 yield ipwdentry(member)
761
852
762 def __repr__(self):
853 def __repr__(self):
763 return "%s.%s(%r)" % \
854 return "%s.%s(%r)" % \
764 (self.__class__.__module__, self.__class__.__name__, self._id)
855 (self.__class__.__module__, self.__class__.__name__, self._id)
765
856
766
857
767 class igrp(Table):
858 class igrp(Table):
768 def __xiter__(self, mode):
859 def __xiter__(self, mode):
769 for entry in grp.getgrall():
860 for entry in grp.getgrall():
770 yield igrpentry(entry.gr_name)
861 yield igrpentry(entry.gr_name)
771
862
772 def __xrepr__(self, mode):
863 def __xrepr__(self, mode):
773 if mode == "header" or mode == "footer":
864 if mode == "header" or mode == "footer":
774 return "%s()" % self.__class__.__name__
865 return "%s()" % self.__class__.__name__
775 return repr(self)
866 return repr(self)
776
867
777
868
778 class Fields(object):
869 class Fields(object):
779 def __init__(self, fieldnames, **fields):
870 def __init__(self, fieldnames, **fields):
780 self.__fieldnames = fieldnames
871 self.__fieldnames = fieldnames
781 for (key, value) in fields.iteritems():
872 for (key, value) in fields.iteritems():
782 setattr(self, key, value)
873 setattr(self, key, value)
783
874
784 def __xattrs__(self, mode):
875 def __xattrs__(self, mode):
785 return self.__fieldnames
876 return self.__fieldnames
786
877
787 def __xrepr__(self, mode):
878 def __xrepr__(self, mode):
788 if mode == "header" or mode == "footer":
879 if mode == "header" or mode == "footer":
789 args = ["%s=%r" % (f, getattr(self, f)) for f in self.__fieldnames]
880 args = ["%s=%r" % (f, getattr(self, f)) for f in self.__fieldnames]
790 return "%s(%r)" % (self.__class__.__name__, ", ".join(args))
881 return "%s(%r)" % (self.__class__.__name__, ", ".join(args))
791 return repr(self)
882 return repr(self)
792
883
793
884
794 class FieldTable(Table, list):
885 class FieldTable(Table, list):
795 def __init__(self, *fields):
886 def __init__(self, *fields):
796 ipipe.Table.__init__(self)
887 ipipe.Table.__init__(self)
797 list.__init__(self)
888 list.__init__(self)
798 self.fields = fields
889 self.fields = fields
799
890
800 def add(self, **fields):
891 def add(self, **fields):
801 self.append(Fields(self, **fields))
892 self.append(Fields(self, **fields))
802
893
803 def __xiter__(self, mode):
894 def __xiter__(self, mode):
804 return list.__iter__(self)
895 return list.__iter__(self)
805
896
806 def __xrepr__(self, mode):
897 def __xrepr__(self, mode):
807 if mode == "header" or mode == "footer":
898 if mode == "header" or mode == "footer":
808 return "FieldTable(%r)" % ", ".join(map(repr, self.fields))
899 return "FieldTable(%r)" % ", ".join(map(repr, self.fields))
809 return repr(self)
900 return repr(self)
810
901
811 def __repr__(self):
902 def __repr__(self):
812 return "<%s.%s object with fields=%r at 0x%x>" % \
903 return "<%s.%s object with fields=%r at 0x%x>" % \
813 (self.__class__.__module__, self.__class__.__name__,
904 (self.__class__.__module__, self.__class__.__name__,
814 ", ".join(map(repr, self.fields)), id(self))
905 ", ".join(map(repr, self.fields)), id(self))
815
906
816
907
817 class ienv(Table):
908 class ienv(Table):
818 def __xiter__(self, mode):
909 def __xiter__(self, mode):
819 fields = ("key", "value")
910 fields = ("key", "value")
820 for (key, value) in os.environ.iteritems():
911 for (key, value) in os.environ.iteritems():
821 yield Fields(fields, key=key, value=value)
912 yield Fields(fields, key=key, value=value)
822
913
823 def __xrepr__(self, mode):
914 def __xrepr__(self, mode):
824 if mode == "header" or mode == "footer":
915 if mode == "header" or mode == "footer":
825 return "%s()" % self.__class__.__name__
916 return "%s()" % self.__class__.__name__
826 return repr(self)
917 return repr(self)
827
918
828
919
829 class icsv(Pipe):
920 class icsv(Pipe):
830 def __init__(self, **csvargs):
921 def __init__(self, **csvargs):
831 self.csvargs = csvargs
922 self.csvargs = csvargs
832
923
833 def __xiter__(self, mode):
924 def __xiter__(self, mode):
834 input = self.input
925 input = self.input
835 if isinstance(input, ifile):
926 if isinstance(input, ifile):
836 input = input.open("rb")
927 input = input.open("rb")
837 reader = csv.reader(input, **self.csvargs)
928 reader = csv.reader(input, **self.csvargs)
838 for line in reader:
929 for line in reader:
839 yield line
930 yield line
840
931
841 def __xrepr__(self, mode):
932 def __xrepr__(self, mode):
842 if mode == "header" or mode == "footer":
933 if mode == "header" or mode == "footer":
843 input = getattr(self, "input", None)
934 input = getattr(self, "input", None)
844 if input is not None:
935 if input is not None:
845 prefix = "%s | " % xrepr(input, mode)
936 prefix = "%s | " % xrepr(input, mode)
846 else:
937 else:
847 prefix = ""
938 prefix = ""
848 args = ", ".join(["%s=%r" % item for item in self.csvargs.iteritems()])
939 args = ", ".join(["%s=%r" % item for item in self.csvargs.iteritems()])
849 return "%s%s(%s)" % (prefix, self.__class__.__name__, args)
940 return "%s%s(%s)" % (prefix, self.__class__.__name__, args)
850 return repr(self)
941 return repr(self)
851
942
852 def __repr__(self):
943 def __repr__(self):
853 args = ", ".join(["%s=%r" % item for item in self.csvargs.iteritems()])
944 args = ", ".join(["%s=%r" % item for item in self.csvargs.iteritems()])
854 return "<%s.%s %s at 0x%x>" % \
945 return "<%s.%s %s at 0x%x>" % \
855 (self.__class__.__module__, self.__class__.__name__, args, id(self))
946 (self.__class__.__module__, self.__class__.__name__, args, id(self))
856
947
857
948
858 class ix(Table):
949 class ix(Table):
859 def __init__(self, cmd):
950 def __init__(self, cmd):
860 self.cmd = cmd
951 self.cmd = cmd
861 self._pipe = None
952 self._pipe = None
862
953
863 def __xiter__(self, mode):
954 def __xiter__(self, mode):
864 self._pipe = os.popen(self.cmd)
955 self._pipe = os.popen(self.cmd)
865 for l in self._pipe:
956 for l in self._pipe:
866 yield l.rstrip("\r\n")
957 yield l.rstrip("\r\n")
867 self._pipe.close()
958 self._pipe.close()
868 self._pipe = None
959 self._pipe = None
869
960
870 def __del__(self):
961 def __del__(self):
871 if self._pipe is not None and not self._pipe.closed:
962 if self._pipe is not None and not self._pipe.closed:
872 self._pipe.close()
963 self._pipe.close()
873 self._pipe = None
964 self._pipe = None
874
965
875 def __xrepr__(self, mode):
966 def __xrepr__(self, mode):
876 if mode == "header" or mode == "footer":
967 if mode == "header" or mode == "footer":
877 return "%s(%r)" % (self.__class__.__name__, self.cmd)
968 return "%s(%r)" % (self.__class__.__name__, self.cmd)
878 return repr(self)
969 return repr(self)
879
970
880 def __repr__(self):
971 def __repr__(self):
881 return "%s.%s(%r)" % \
972 return "%s.%s(%r)" % \
882 (self.__class__.__module__, self.__class__.__name__, self.cmd)
973 (self.__class__.__module__, self.__class__.__name__, self.cmd)
883
974
884
975
885 class ifilter(Pipe):
976 class ifilter(Pipe):
886 def __init__(self, expr):
977 def __init__(self, expr):
887 self.expr = expr
978 self.expr = expr
888
979
889 def __xiter__(self, mode):
980 def __xiter__(self, mode):
890 if callable(self.expr):
981 if callable(self.expr):
891 for item in xiter(self.input, mode):
982 for item in xiter(self.input, mode):
892 try:
983 try:
893 if self.expr(item):
984 if self.expr(item):
894 yield item
985 yield item
895 except (KeyboardInterrupt, SystemExit):
986 except (KeyboardInterrupt, SystemExit):
896 raise
987 raise
897 except Exception:
988 except Exception:
898 pass # Ignore errors
989 pass # Ignore errors
899 else:
990 else:
900 for item in xiter(self.input, mode):
991 for item in xiter(self.input, mode):
901 try:
992 try:
902 if eval(self.expr, globals(), _AttrNamespace(item)):
993 if eval(self.expr, globals(), _AttrNamespace(item)):
903 yield item
994 yield item
904 except (KeyboardInterrupt, SystemExit):
995 except (KeyboardInterrupt, SystemExit):
905 raise
996 raise
906 except Exception:
997 except Exception:
907 pass # Ignore errors
998 pass # Ignore errors
908
999
909 def __xrepr__(self, mode):
1000 def __xrepr__(self, mode):
910 if mode == "header" or mode == "footer":
1001 if mode == "header" or mode == "footer":
911 input = getattr(self, "input", None)
1002 input = getattr(self, "input", None)
912 if input is not None:
1003 if input is not None:
913 prefix = "%s | " % xrepr(input, mode)
1004 prefix = "%s | " % xrepr(input, mode)
914 else:
1005 else:
915 prefix = ""
1006 prefix = ""
916 return "%s%s(%s)"% \
1007 return "%s%s(%s)"% \
917 (prefix, self.__class__.__name__, xrepr(self.expr, mode))
1008 (prefix, self.__class__.__name__, xrepr(self.expr, mode))
918 return repr(self)
1009 return repr(self)
919
1010
920 def __repr__(self):
1011 def __repr__(self):
921 return "<%s.%s expr=%r at 0x%x>" % \
1012 return "<%s.%s expr=%r at 0x%x>" % \
922 (self.__class__.__module__, self.__class__.__name__,
1013 (self.__class__.__module__, self.__class__.__name__,
923 self.expr, id(self))
1014 self.expr, id(self))
924
1015
925
1016
926 class ieval(Pipe):
1017 class ieval(Pipe):
927 def __init__(self, expr):
1018 def __init__(self, expr):
928 self.expr = expr
1019 self.expr = expr
929
1020
930 def __xiter__(self, mode):
1021 def __xiter__(self, mode):
931 if callable(self.expr):
1022 if callable(self.expr):
932 for item in xiter(self.input, mode):
1023 for item in xiter(self.input, mode):
933 try:
1024 try:
934 yield self.expr(item)
1025 yield self.expr(item)
935 except (KeyboardInterrupt, SystemExit):
1026 except (KeyboardInterrupt, SystemExit):
936 raise
1027 raise
937 except Exception:
1028 except Exception:
938 pass # Ignore errors
1029 pass # Ignore errors
939 else:
1030 else:
940 for item in xiter(self.input, mode):
1031 for item in xiter(self.input, mode):
941 try:
1032 try:
942 yield eval(self.expr, globals(), _AttrNamespace(item))
1033 yield eval(self.expr, globals(), _AttrNamespace(item))
943 except (KeyboardInterrupt, SystemExit):
1034 except (KeyboardInterrupt, SystemExit):
944 raise
1035 raise
945 except Exception:
1036 except Exception:
946 pass # Ignore errors
1037 pass # Ignore errors
947
1038
948 def __xrepr__(self, mode):
1039 def __xrepr__(self, mode):
949 if mode == "header" or mode == "footer":
1040 if mode == "header" or mode == "footer":
950 input = getattr(self, "input", None)
1041 input = getattr(self, "input", None)
951 if input is not None:
1042 if input is not None:
952 prefix = "%s | " % xrepr(input, mode)
1043 prefix = "%s | " % xrepr(input, mode)
953 else:
1044 else:
954 prefix = ""
1045 prefix = ""
955 return "%s%s(%s)" % \
1046 return "%s%s(%s)" % \
956 (prefix, self.__class__.__name__, xrepr(self.expr, mode))
1047 (prefix, self.__class__.__name__, xrepr(self.expr, mode))
957 return repr(self)
1048 return repr(self)
958
1049
959 def __repr__(self):
1050 def __repr__(self):
960 return "<%s.%s expr=%r at 0x%x>" % \
1051 return "<%s.%s expr=%r at 0x%x>" % \
961 (self.__class__.__module__, self.__class__.__name__,
1052 (self.__class__.__module__, self.__class__.__name__,
962 self.expr, id(self))
1053 self.expr, id(self))
963
1054
964
1055
965 class ienum(Pipe):
1056 class ienum(Pipe):
966 def __xiter__(self, mode):
1057 def __xiter__(self, mode):
967 fields = ("index", "object")
1058 fields = ("index", "object")
968 for (index, object) in enumerate(xiter(self.input, mode)):
1059 for (index, object) in enumerate(xiter(self.input, mode)):
969 yield Fields(fields, index=index, object=object)
1060 yield Fields(fields, index=index, object=object)
970
1061
971
1062
972 class isort(Pipe):
1063 class isort(Pipe):
973 def __init__(self, key, reverse=False):
1064 def __init__(self, key, reverse=False):
974 self.key = key
1065 self.key = key
975 self.reverse = reverse
1066 self.reverse = reverse
976
1067
977 def __xiter__(self, mode):
1068 def __xiter__(self, mode):
978 if callable(self.key):
1069 if callable(self.key):
979 items = sorted(
1070 items = sorted(
980 xiter(self.input, mode),
1071 xiter(self.input, mode),
981 key=self.key,
1072 key=self.key,
982 reverse=self.reverse
1073 reverse=self.reverse
983 )
1074 )
984 else:
1075 else:
985 def key(item):
1076 def key(item):
986 return eval(self.key, globals(), _AttrNamespace(item))
1077 return eval(self.key, globals(), _AttrNamespace(item))
987 items = sorted(
1078 items = sorted(
988 xiter(self.input, mode),
1079 xiter(self.input, mode),
989 key=key,
1080 key=key,
990 reverse=self.reverse
1081 reverse=self.reverse
991 )
1082 )
992 for item in items:
1083 for item in items:
993 yield item
1084 yield item
994
1085
995 def __xrepr__(self, mode):
1086 def __xrepr__(self, mode):
996 if mode == "header" or mode == "footer":
1087 if mode == "header" or mode == "footer":
997 input = getattr(self, "input", None)
1088 input = getattr(self, "input", None)
998 if input is not None:
1089 if input is not None:
999 prefix = "%s | " % xrepr(input, mode)
1090 prefix = "%s | " % xrepr(input, mode)
1000 else:
1091 else:
1001 prefix = ""
1092 prefix = ""
1002 if self.reverse:
1093 if self.reverse:
1003 return "%s%s(%r, %r)" % \
1094 return "%s%s(%r, %r)" % \
1004 (prefix, self.__class__.__name__, self.key, self.reverse)
1095 (prefix, self.__class__.__name__, self.key, self.reverse)
1005 else:
1096 else:
1006 return "%s%s(%r)" % (prefix, self.__class__.__name__, self.key)
1097 return "%s%s(%r)" % (prefix, self.__class__.__name__, self.key)
1007 return repr(self)
1098 return repr(self)
1008
1099
1009 def __repr__(self):
1100 def __repr__(self):
1010 return "<%s.%s key=%r reverse=%r at 0x%x>" % \
1101 return "<%s.%s key=%r reverse=%r at 0x%x>" % \
1011 (self.__class__.__module__, self.__class__.__name__,
1102 (self.__class__.__module__, self.__class__.__name__,
1012 self.key, self.reverse, id(self))
1103 self.key, self.reverse, id(self))
1013
1104
1014
1105
1015 tab = 3 # for expandtabs()
1106 tab = 3 # for expandtabs()
1016
1107
1017 def _format(field):
1108 def _format(field):
1018 if isinstance(field, str):
1109 if isinstance(field, str):
1019 text = repr(field.expandtabs(tab))[1:-1]
1110 text = repr(field.expandtabs(tab))[1:-1]
1020 elif isinstance(field, unicode):
1111 elif isinstance(field, unicode):
1021 text = repr(field.expandtabs(tab))[2:-1]
1112 text = repr(field.expandtabs(tab))[2:-1]
1022 elif isinstance(field, datetime.datetime):
1113 elif isinstance(field, datetime.datetime):
1023 # Don't use strftime() here, as this requires year >= 1900
1114 # Don't use strftime() here, as this requires year >= 1900
1024 text = "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
1115 text = "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
1025 (field.year, field.month, field.day,
1116 (field.year, field.month, field.day,
1026 field.hour, field.minute, field.second, field.microsecond)
1117 field.hour, field.minute, field.second, field.microsecond)
1027 elif isinstance(field, datetime.date):
1118 elif isinstance(field, datetime.date):
1028 text = "%04d-%02d-%02d" % (field.year, field.month, field.day)
1119 text = "%04d-%02d-%02d" % (field.year, field.month, field.day)
1029 else:
1120 else:
1030 text = repr(field)
1121 text = repr(field)
1031 return text
1122 return text
1032
1123
1033
1124
1034 class Display(object):
1125 class Display(object):
1035 class __metaclass__(type):
1126 class __metaclass__(type):
1036 def __ror__(self, input):
1127 def __ror__(self, input):
1037 return input | self()
1128 return input | self()
1038
1129
1039 def __ror__(self, input):
1130 def __ror__(self, input):
1040 self.input = input
1131 self.input = input
1041 return self
1132 return self
1042
1133
1043 def display(self):
1134 def display(self):
1044 pass
1135 pass
1045
1136
1046
1137
1047 class iless(Display):
1138 class iless(Display):
1048 cmd = "less --quit-if-one-screen --LONG-PROMPT --LINE-NUMBERS --chop-long-lines --shift=8 --RAW-CONTROL-CHARS"
1139 cmd = "less --quit-if-one-screen --LONG-PROMPT --LINE-NUMBERS --chop-long-lines --shift=8 --RAW-CONTROL-CHARS"
1049
1140
1050 def display(self):
1141 def display(self):
1051 try:
1142 try:
1052 pager = os.popen(self.cmd, "w")
1143 pager = os.popen(self.cmd, "w")
1053 try:
1144 try:
1054 for item in xiter(self.input, "default"):
1145 for item in xiter(self.input, "default"):
1055 attrs = xattrs(item, "default")
1146 attrs = xattrs(item, "default")
1056 attrs = ["%s=%s" % (a, _format(_getattr(item, a))) for a in attrs]
1147 attrs = ["%s=%s" % (a, _format(_getattr(item, a))) for a in attrs]
1057 pager.write(" ".join(attrs))
1148 pager.write(" ".join(attrs))
1058 pager.write("\n")
1149 pager.write("\n")
1059 finally:
1150 finally:
1060 pager.close()
1151 pager.close()
1061 except Exception, exc:
1152 except Exception, exc:
1062 print "%s: %s" % (exc.__class__.__name__, str(exc))
1153 print "%s: %s" % (exc.__class__.__name__, str(exc))
1063
1154
1064
1155
1065 class idump(Display):
1156 class idump(Display):
1066 def __init__(self, *attrs):
1157 def __init__(self, *attrs):
1067 self.attrs = attrs
1158 self.attrs = attrs
1068 self.headerpadchar = " "
1159 self.headerpadchar = " "
1069 self.headersepchar = "|"
1160 self.headersepchar = "|"
1070 self.datapadchar = " "
1161 self.datapadchar = " "
1071 self.datasepchar = "|"
1162 self.datasepchar = "|"
1072
1163
1073 def display(self):
1164 def display(self):
1074 stream = sys.stdout
1165 stream = sys.stdout
1075 if self.attrs:
1166 if self.attrs:
1076 rows = []
1167 rows = []
1077 colwidths = dict([(a, len(_attrname(a))) for a in self.attrs])
1168 colwidths = dict([(a, len(_attrname(a))) for a in self.attrs])
1078
1169
1079 for item in xiter(self.input, "default"):
1170 for item in xiter(self.input, "default"):
1080 row = {}
1171 row = {}
1081 for attrname in self.attrs:
1172 for attrname in self.attrs:
1082 value = _getattr(item, attrname, None)
1173 value = _getattr(item, attrname, None)
1083 text = _format(value)
1174 text = _format(value)
1084 colwidths[attrname] = max(colwidths[attrname], width)
1175 colwidths[attrname] = max(colwidths[attrname], width)
1085 row[attrname] = (value, text)
1176 row[attrname] = (value, text)
1086 rows.append(row)
1177 rows.append(row)
1087
1178
1179 stream.write("\n")
1088 for (i, attrname) in enumerate(self.attrs):
1180 for (i, attrname) in enumerate(self.attrs):
1089 stream.write(_attrname(attrname))
1181 stream.write(_attrname(attrname))
1090 spc = colwidths[attrname] - len(_attrname(attrname))
1182 spc = colwidths[attrname] - len(_attrname(attrname))
1091 if i < len(colwidths)-1:
1183 if i < len(colwidths)-1:
1092 if spc>0:
1184 if spc>0:
1093 stream.write(self.headerpadchar * spc)
1185 stream.write(self.headerpadchar * spc)
1094 stream.write(self.headersepchar)
1186 stream.write(self.headersepchar)
1095 stream.write("\n")
1187 stream.write("\n")
1096
1188
1097 for row in rows:
1189 for row in rows:
1098 for (i, attrname) in enumerate(self.attrs):
1190 for (i, attrname) in enumerate(self.attrs):
1099 (value, text) = row[attrname]
1191 (value, text) = row[attrname]
1100 spc = colwidths[attrname] - len(text)
1192 spc = colwidths[attrname] - len(text)
1101 if isinstance(value, (int, long)):
1193 if isinstance(value, (int, long)):
1102 if spc>0:
1194 if spc>0:
1103 stream.write(self.datapadchar*spc)
1195 stream.write(self.datapadchar*spc)
1104 stream.write(text)
1196 stream.write(text)
1105 else:
1197 else:
1106 stream.write(text)
1198 stream.write(text)
1107 if i < len(colwidths)-1:
1199 if i < len(colwidths)-1:
1108 if spc>0:
1200 if spc>0:
1109 stream.write(self.datapadchar*spc)
1201 stream.write(self.datapadchar*spc)
1110 if i < len(colwidths)-1:
1202 if i < len(colwidths)-1:
1111 stream.write(self.datasepchar)
1203 stream.write(self.datasepchar)
1112 stream.write("\n")
1204 stream.write("\n")
1113 else:
1205 else:
1114 allattrs = []
1206 allattrs = []
1115 allattrset = set()
1207 allattrset = set()
1116 colwidths = {}
1208 colwidths = {}
1117 rows = []
1209 rows = []
1118 for item in xiter(self.input, "default"):
1210 for item in xiter(self.input, "default"):
1119 row = {}
1211 row = {}
1120 attrs = xattrs(item, "default")
1212 attrs = xattrs(item, "default")
1121 for attrname in attrs:
1213 for attrname in attrs:
1122 if attrname not in allattrset:
1214 if attrname not in allattrset:
1123 allattrs.append(attrname)
1215 allattrs.append(attrname)
1124 allattrset.add(attrname)
1216 allattrset.add(attrname)
1125 colwidths[attrname] = len(_attrname(attrname))
1217 colwidths[attrname] = len(_attrname(attrname))
1126 value = _getattr(item, attrname, None)
1218 value = _getattr(item, attrname, None)
1127 text = _format(value)
1219 text = _format(value)
1128 colwidths[attrname] = max(colwidths[attrname], len(text))
1220 colwidths[attrname] = max(colwidths[attrname], len(text))
1129 row[attrname] = (value, text)
1221 row[attrname] = (value, text)
1130 rows.append(row)
1222 rows.append(row)
1131
1223
1132 for (i, attrname) in enumerate(allattrs):
1224 for (i, attrname) in enumerate(allattrs):
1133 stream.write(_attrname(attrname))
1225 stream.write(_attrname(attrname))
1134 spc = colwidths[attrname] - len(_attrname(attrname))
1226 spc = colwidths[attrname] - len(_attrname(attrname))
1135 if i < len(colwidths)-1:
1227 if i < len(colwidths)-1:
1136 if spc>0:
1228 if spc>0:
1137 stream.write(self.headerpadchar*spc)
1229 stream.write(self.headerpadchar*spc)
1138 stream.write(self.headersepchar)
1230 stream.write(self.headersepchar)
1139 stream.write("\n")
1231 stream.write("\n")
1140
1232
1141 for row in rows:
1233 for row in rows:
1142 for (i, attrname) in enumerate(attrs):
1234 for (i, attrname) in enumerate(attrs):
1143 (value, text) = row.get(attrname, ("", ""))
1235 (value, text) = row.get(attrname, ("", ""))
1144 spc = colwidths[attrname] - len(text)
1236 spc = colwidths[attrname] - len(text)
1145 if isinstance(value, (int, long)):
1237 if isinstance(value, (int, long)):
1146 if spc>0:
1238 if spc>0:
1147 stream.write(self.datapadchar*spc)
1239 stream.write(self.datapadchar*spc)
1148 stream.write(text)
1240 stream.write(text)
1149 else:
1241 else:
1150 stream.write(text)
1242 stream.write(text)
1151 if i < len(colwidths)-1:
1243 if i < len(colwidths)-1:
1152 if spc>0:
1244 if spc>0:
1153 stream.write(self.datapadchar*spc)
1245 stream.write(self.datapadchar*spc)
1154 if i < len(colwidths)-1:
1246 if i < len(colwidths)-1:
1155 stream.write(self.datasepchar)
1247 stream.write(self.datasepchar)
1156 stream.write("\n")
1248 stream.write("\n")
1157
1249
1158
1250
1159 class XMode(object):
1251 class XMode(object):
1252 """
1253 An ``XMode`` object describes one enter mode available for an object
1254 """
1160 def __init__(self, object, mode, title=None, description=None):
1255 def __init__(self, object, mode, title=None, description=None):
1256 """
1257 Create a new ``XMode`` object for the object ``object``. This object
1258 must support the enter mode ``mode`` (i.e. ``object.__xiter__(mode)``
1259 must return an iterable). ``title`` and ``description`` will be
1260 displayed in the browser when selecting among the available modes.
1261 """
1161 self.object = object
1262 self.object = object
1162 self.mode = mode
1263 self.mode = mode
1163 self.title = title
1264 self.title = title
1164 self.description = description
1265 self.description = description
1165
1266
1166 def __repr__(self):
1267 def __repr__(self):
1167 return "<%s.%s object mode=%r at 0x%x>" % \
1268 return "<%s.%s object mode=%r at 0x%x>" % \
1168 (self.__class__.__module__, self.__class__.__name__,
1269 (self.__class__.__module__, self.__class__.__name__,
1169 self.mode, id(self))
1270 self.mode, id(self))
1170
1271
1171 def __xrepr__(self, mode):
1272 def __xrepr__(self, mode):
1172 if mode == "header" or mode == "footer":
1273 if mode == "header" or mode == "footer":
1173 return self.title
1274 return self.title
1174 return repr(self)
1275 return repr(self)
1175
1276
1176 def __xattrs__(self, mode):
1277 def __xattrs__(self, mode):
1177 if mode == "detail":
1278 if mode == "detail":
1178 return ("object", "mode", "title", "description")
1279 return ("object", "mode", "title", "description")
1179 return ("title", "description")
1280 return ("title", "description")
1180
1281
1181 def __xiter__(self, mode):
1282 def __xiter__(self, mode):
1182 return xiter(self.object, self.mode)
1283 return xiter(self.object, self.mode)
1183
1284
1184
1285
1185 class XAttr(object):
1286 class XAttr(object):
1186 def __init__(self, object, name):
1287 def __init__(self, object, name):
1187 self.name = _attrname(name)
1288 self.name = _attrname(name)
1188
1289
1189 try:
1290 try:
1190 self.value = _getattr(object, name)
1291 self.value = _getattr(object, name)
1191 except (KeyboardInterrupt, SystemExit):
1292 except (KeyboardInterrupt, SystemExit):
1192 raise
1293 raise
1193 except Exception, exc:
1294 except Exception, exc:
1194 if exc.__class__.__module__ == "exceptions":
1295 if exc.__class__.__module__ == "exceptions":
1195 self.value = exc.__class__.__name__
1296 self.value = exc.__class__.__name__
1196 else:
1297 else:
1197 self.value = "%s.%s" % \
1298 self.value = "%s.%s" % \
1198 (exc.__class__.__module__, exc.__class__.__name__)
1299 (exc.__class__.__module__, exc.__class__.__name__)
1199 self.type = self.value
1300 self.type = self.value
1200 else:
1301 else:
1201 t = type(self.value)
1302 t = type(self.value)
1202 if t.__module__ == "__builtin__":
1303 if t.__module__ == "__builtin__":
1203 self.type = t.__name__
1304 self.type = t.__name__
1204 else:
1305 else:
1205 self.type = "%s.%s" % (t.__module__, t.__name__)
1306 self.type = "%s.%s" % (t.__module__, t.__name__)
1206
1307
1207 doc = None
1308 doc = None
1208 if isinstance(name, basestring):
1309 if isinstance(name, basestring):
1209 try:
1310 try:
1210 meta = getattr(type(object), name)
1311 meta = getattr(type(object), name)
1211 except AttributeError:
1312 except AttributeError:
1212 pass
1313 pass
1213 else:
1314 else:
1214 if isinstance(meta, property):
1315 if isinstance(meta, property):
1215 self.doc = getattr(meta, "__doc__", None)
1316 self.doc = getattr(meta, "__doc__", None)
1216 elif callable(name):
1317 elif callable(name):
1217 try:
1318 try:
1218 self.doc = name.__doc__
1319 self.doc = name.__doc__
1219 except AttributeError:
1320 except AttributeError:
1220 pass
1321 pass
1221
1322
1222 def __xattrs__(self, mode):
1323 def __xattrs__(self, mode):
1223 return ("name", "type", "doc", "value")
1324 return ("name", "type", "doc", "value")
1224
1325
1225
1326
1226 _ibrowse_help = """
1327 _ibrowse_help = """
1227 down
1328 down
1228 Move the cursor to the next line.
1329 Move the cursor to the next line.
1229
1330
1230 up
1331 up
1231 Move the cursor to the previous line.
1332 Move the cursor to the previous line.
1232
1333
1233 pagedown
1334 pagedown
1234 Move the cursor down one page (minus overlap).
1335 Move the cursor down one page (minus overlap).
1235
1336
1236 pageup
1337 pageup
1237 Move the cursor up one page.
1338 Move the cursor up one page (minus overlap).
1238
1339
1239 left
1340 left
1240 Move the cursor left.
1341 Move the cursor left.
1241
1342
1242 right
1343 right
1243 Move the cursor right.
1344 Move the cursor right.
1244
1345
1245 home
1346 home
1246 Move the cursor to the first column.
1347 Move the cursor to the first column.
1247
1348
1248 end
1349 end
1249 Move the cursor to the last column.
1350 Move the cursor to the last column.
1250
1351
1251 pick
1352 pick
1252 'Pick' the object under the cursor (i.e. the row the cursor is on). This leaves
1353 'Pick' the object under the cursor (i.e. the row the cursor is on). This leaves
1253 the browser and returns the picked object to the caller. (In IPython this object
1354 the browser and returns the picked object to the caller. (In IPython this object
1254 will be available as the '_' variable.)
1355 will be available as the '_' variable.)
1255
1356
1256 pickattr
1357 pickattr
1257 'Pick' the attribute under the cursor (i.e. the row/column the cursor is on).
1358 'Pick' the attribute under the cursor (i.e. the row/column the cursor is on).
1258
1359
1259 pickallattrs
1360 pickallattrs
1260 Pick' the complete column under the cursor (i.e. the attribute under the cursor
1361 Pick' the complete column under the cursor (i.e. the attribute under the cursor)
1261 from all currently fetched objects). The attributes will be returned as a list.
1362 from all currently fetched objects. These attributes will be returned as a list.
1262
1363
1263 tooglemark
1364 tooglemark
1264 Mark/unmark the object under the cursor. Marked objects have a '!' after the
1365 Mark/unmark the object under the cursor. Marked objects have a '!' after the
1265 row number).
1366 row number).
1266
1367
1267 pickmarked
1368 pickmarked
1268 'Pick' marked objects. Marked objects will be returned as a list.
1369 'Pick' marked objects. Marked objects will be returned as a list.
1269
1370
1270 pickmarkedattr
1371 pickmarkedattr
1271 'Pick' the attribute under the cursor from all marked objects (This returns a
1372 'Pick' the attribute under the cursor from all marked objects (This returns a
1272 list)
1373 list).
1273
1374
1274 enterdefault
1375 enterdefault
1275 Enter the object under the cursor. (what this mean depends on the object
1376 Enter the object under the cursor. (what this mean depends on the object
1276 itself). This opens a new browser 'level'.
1377 itself (i.e. how it implements the '__xiter__' method). This opens a new browser
1378 'level'.
1277
1379
1278 enter
1380 enter
1279 Enter the object under the cursor. If the object provides different enter modes
1381 Enter the object under the cursor. If the object provides different enter modes
1280 a menu of all modes will be presented, choice one and enter it (via the 'enter'
1382 a menu of all modes will be presented; choice one and enter it (via the 'enter'
1281 or 'enterdefault' command),
1383 or 'enterdefault' command).
1282
1384
1283 enterattr
1385 enterattr
1284 Enter the attribute under the cursor.
1386 Enter the attribute under the cursor.
1285
1387
1286 leave
1388 leave
1287 Leave the current browser level and go back to the previous one.
1389 Leave the current browser level and go back to the previous one.
1288
1390
1289 detail
1391 detail
1290 Show a detail view of the object under the cursor. This shows the name, type,
1392 Show a detail view of the object under the cursor. This shows the name, type,
1291 doc string and value of the object attributes (and it might show more attributes
1393 doc string and value of the object attributes (and it might show more attributes
1292 than in the list view; depending on the object).
1394 than in the list view, depending on the object).
1293
1395
1294 markrange
1396 markrange
1295 Mark all objects from the last marked object before the current cursor position
1397 Mark all objects from the last marked object before the current cursor position
1296 to the cursor position.
1398 to the cursor position.
1297
1399
1298 sortattrasc
1400 sortattrasc
1299 Sort the objects (in ascending order) using the attribute under the cursor as
1401 Sort the objects (in ascending order) using the attribute under the cursor as
1300 the sort key.
1402 the sort key.
1301
1403
1302 sortattrdesc
1404 sortattrdesc
1303 Sort the objects (in descending order) using the attribute under the cursor as
1405 Sort the objects (in descending order) using the attribute under the cursor as
1304 the sort key.
1406 the sort key.
1305
1407
1306 help
1408 help
1307 This screen.
1409 This screen.
1308 """
1410 """
1309
1411
1310
1412
1311 if curses is not None:
1413 if curses is not None:
1312 class UnassignedKeyError(Exception):
1414 class UnassignedKeyError(Exception):
1313 pass
1415 """
1416 Exception that is used for reporting unassigned keys.
1417 """
1314
1418
1315
1419
1316 class UnknownCommandError(Exception):
1420 class UnknownCommandError(Exception):
1317 pass
1421 """
1422 Exception that is used for reporting unknown command (this should never
1423 happen).
1424 """
1318
1425
1319
1426
1320 class CommandError(Exception):
1427 class CommandError(Exception):
1321 pass
1428 """
1429 Exception that is used for reporting that a command can't be executed.
1430 """
1322
1431
1323
1432
1324 class Style(object):
1433 class Style(object):
1434 """
1435 Store foreground color, background color and attribute (bold, underlined
1436 etc.) for ``curses``.
1437 """
1325 __slots__ = ("fg", "bg", "attrs")
1438 __slots__ = ("fg", "bg", "attrs")
1326
1439
1327 def __init__(self, fg, bg, attrs=0):
1440 def __init__(self, fg, bg, attrs=0):
1328 self.fg = fg
1441 self.fg = fg
1329 self.bg = bg
1442 self.bg = bg
1330 self.attrs = attrs
1443 self.attrs = attrs
1331
1444
1332
1445
1333 class _BrowserCachedItem(object):
1446 class _BrowserCachedItem(object):
1447 # This is used internally by ``ibrowse`` to store a item together with its
1448 # marked status.
1334 __slots__ = ("item", "marked")
1449 __slots__ = ("item", "marked")
1335
1450
1336 def __init__(self, item):
1451 def __init__(self, item):
1337 self.item = item
1452 self.item = item
1338 self.marked = False
1453 self.marked = False
1339
1454
1340
1455
1341 class _BrowserHelp(object):
1456 class _BrowserHelp(object):
1457 # This is used internally by ``ibrowse`` for displaying the help screen.
1342 def __init__(self, browser):
1458 def __init__(self, browser):
1343 self.browser = browser
1459 self.browser = browser
1344
1460
1345 def __xrepr__(self, mode):
1461 def __xrepr__(self, mode):
1346 if mode == "header" or mode == "footer":
1462 if mode == "header" or mode == "footer":
1347 return "ibrowse help screen"
1463 return "ibrowse help screen"
1348 return repr(self)
1464 return repr(self)
1349
1465
1350 def __xiter__(self, mode):
1466 def __xiter__(self, mode):
1351 # Get reverse key mapping
1467 # Get reverse key mapping
1352 allkeys = {}
1468 allkeys = {}
1353 for (key, cmd) in self.browser.keymap.iteritems():
1469 for (key, cmd) in self.browser.keymap.iteritems():
1354 allkeys.setdefault(cmd, []).append(key)
1470 allkeys.setdefault(cmd, []).append(key)
1355
1471
1356 fields = ("key", "command", "description")
1472 fields = ("key", "command", "description")
1357
1473
1358 for (i, command) in enumerate(_ibrowse_help.strip().split("\n\n")):
1474 for (i, command) in enumerate(_ibrowse_help.strip().split("\n\n")):
1359 if i:
1475 if i:
1360 yield Fields(fields, key="", command="", description="")
1476 yield Fields(fields, key="", command="", description="")
1361
1477
1362 (name, description) = command.split("\n", 1)
1478 (name, description) = command.split("\n", 1)
1363 keys = allkeys.get(name, [])
1479 keys = allkeys.get(name, [])
1364 lines = textwrap.wrap(description, 50)
1480 lines = textwrap.wrap(description, 50)
1365
1481
1366 for i in xrange(max(len(keys), len(lines))):
1482 for i in xrange(max(len(keys), len(lines))):
1367 if i:
1483 if i:
1368 name = ""
1484 name = ""
1369 try:
1485 try:
1370 key = self.browser.keylabel(keys[i])
1486 key = self.browser.keylabel(keys[i])
1371 except IndexError:
1487 except IndexError:
1372 key = ""
1488 key = ""
1373 try:
1489 try:
1374 line = lines[i]
1490 line = lines[i]
1375 except IndexError:
1491 except IndexError:
1376 line = ""
1492 line = ""
1377 yield Fields(fields, key=key, command=name, description=line)
1493 yield Fields(fields, key=key, command=name, description=line)
1378
1494
1379
1495
1380 class _BrowserLevel(object):
1496 class _BrowserLevel(object):
1497 # This is used internally to store the state (iterator, fetch items,
1498 # position of cursor and screen, etc.) of one browser level
1499 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
1500 # a stack.
1381 def __init__(self, browser, input, iterator, mainsizey, *attrs):
1501 def __init__(self, browser, input, iterator, mainsizey, *attrs):
1382 self.browser = browser
1502 self.browser = browser
1383 self.input = input
1503 self.input = input
1384 self.header = xrepr(input, "header")
1504 self.header = xrepr(input, "header")
1385 self.iterator = iterator # iterator for the input
1505 self.iterator = iterator # iterator for the input
1386 self.exhausted = False # is the iterator exhausted?
1506 self.exhausted = False # is the iterator exhausted?
1387 self.attrs = attrs
1507 self.attrs = attrs
1388 self.items = deque()
1508 self.items = deque()
1389 self.marked = 0 # Number of marked objects
1509 self.marked = 0 # Number of marked objects
1390 self.cury = 0 # Vertical cursor position
1510 self.cury = 0 # Vertical cursor position
1391 self.curx = 0 # Horizontal cursor position
1511 self.curx = 0 # Horizontal cursor position
1392 self.datastarty = 0 # Index of first data line
1512 self.datastarty = 0 # Index of first data line
1393 self.datastartx = 0 # Index of first data column
1513 self.datastartx = 0 # Index of first data column
1394 self.mainsizey = mainsizey # height of the data display area
1514 self.mainsizey = mainsizey # height of the data display area
1395 self.mainsizex = 0 # width of the data display area
1515 self.mainsizex = 0 # width of the data display area
1396 self.numbersizex = 0 # Size of number at the left edge of the screen
1516 self.numbersizex = 0 # Size of number at the left edge of the screen
1397 self.displayattrs = [] # Attribute names to display (in this order)
1517 self.displayattrs = [] # Attribute names to display (in this order)
1398 self.displayattr = _default # Name of attribute under the cursor
1518 self.displayattr = _default # Name of attribute under the cursor
1399 self.colwidths = {} # Maps attribute names to column widths
1519 self.colwidths = {} # Maps attribute names to column widths
1400
1520
1401 self.fetch(mainsizey)
1521 self.fetch(mainsizey)
1402 self.calcdisplayattrs()
1522 self.calcdisplayattrs()
1403 # formatted attributes for the items on screen
1523 # formatted attributes for the items on screen
1404 # (i.e. self.items[self.datastarty:self.datastarty+self.mainsizey])
1524 # (i.e. self.items[self.datastarty:self.datastarty+self.mainsizey])
1405 self.displayrows = [self.getrow(i) for i in xrange(len(self.items))]
1525 self.displayrows = [self.getrow(i) for i in xrange(len(self.items))]
1406 self.calcwidths()
1526 self.calcwidths()
1407 self.calcdisplayattr()
1527 self.calcdisplayattr()
1408
1528
1409 def fetch(self, count):
1529 def fetch(self, count):
1530 # Try to fill ``self.items`` with at least ``count`` objects.
1410 have = len(self.items)
1531 have = len(self.items)
1411 while not self.exhausted and have < count:
1532 while not self.exhausted and have < count:
1412 try:
1533 try:
1413 item = self.iterator.next()
1534 item = self.iterator.next()
1414 except StopIteration:
1535 except StopIteration:
1415 self.exhausted = True
1536 self.exhausted = True
1416 break
1537 break
1417 else:
1538 else:
1418 have += 1
1539 have += 1
1419 self.items.append(_BrowserCachedItem(item))
1540 self.items.append(_BrowserCachedItem(item))
1420
1541
1421 def calcdisplayattrs(self):
1542 def calcdisplayattrs(self):
1543 # Calculate which attributes are available from the objects that are
1544 # currently visible on screen (and store it in ``self.displayattrs``)
1422 attrnames = set()
1545 attrnames = set()
1546 # If the browser object specifies a fixed list of attributes,
1547 # simply use it.
1423 if self.attrs:
1548 if self.attrs:
1424 self.displayattrs = self.attrs
1549 self.displayattrs = self.attrs
1425 else:
1550 else:
1426 self.displayattrs = []
1551 self.displayattrs = []
1427 endy = min(self.datastarty+self.mainsizey, len(self.items))
1552 endy = min(self.datastarty+self.mainsizey, len(self.items))
1428 for i in xrange(self.datastarty, endy):
1553 for i in xrange(self.datastarty, endy):
1429 for attrname in xattrs(self.items[i].item, "default"):
1554 for attrname in xattrs(self.items[i].item, "default"):
1430 if attrname not in attrnames:
1555 if attrname not in attrnames:
1431 self.displayattrs.append(attrname)
1556 self.displayattrs.append(attrname)
1432 attrnames.add(attrname)
1557 attrnames.add(attrname)
1433
1558
1434 def getrow(self, i):
1559 def getrow(self, i):
1560 # Return a dictinary with the attributes for the object
1561 # ``self.items[i]``. Attribute names are taken from
1562 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
1563 # called before.
1435 row = {}
1564 row = {}
1436 item = self.items[i].item
1565 item = self.items[i].item
1437 for attrname in self.displayattrs:
1566 for attrname in self.displayattrs:
1438 try:
1567 try:
1439 value = _getattr(item, attrname, _default)
1568 value = _getattr(item, attrname, _default)
1440 except (KeyboardInterrupt, SystemExit):
1569 except (KeyboardInterrupt, SystemExit):
1441 raise
1570 raise
1442 except Exception, exc:
1571 except Exception, exc:
1443 row[attrname] = self.browser.format(exc)
1572 row[attrname] = self.browser.format(exc)
1444 else:
1573 else:
1445 # only store attribute if it exists
1574 # only store attribute if it exists
1446 if value is not _default:
1575 if value is not _default:
1447 row[attrname] = self.browser.format(value)
1576 row[attrname] = self.browser.format(value)
1448 return row
1577 return row
1449
1578
1450 def calcwidths(self):
1579 def calcwidths(self):
1451 # Recalculate the displayed fields and their width
1580 # Recalculate the displayed fields and their width.
1581 # ``calcdisplayattrs()'' must have been called and the cache
1582 # for attributes of the objects on screen (``self.displayrows``)
1583 # must have been filled. This returns a dictionary mapping
1584 # colmn names to width.
1452 self.colwidths = {}
1585 self.colwidths = {}
1453 for row in self.displayrows:
1586 for row in self.displayrows:
1454 for attrname in self.displayattrs:
1587 for attrname in self.displayattrs:
1455 (align, text, style) = row.get(attrname, (2, "", None))
1588 (align, text, style) = row.get(attrname, (2, "", None))
1456 # always add attribute to colwidths, even if it doesn't exist
1589 # always add attribute to colwidths, even if it doesn't exist
1457 if attrname not in self.colwidths:
1590 if attrname not in self.colwidths:
1458 self.colwidths[attrname] = len(_attrname(attrname))
1591 self.colwidths[attrname] = len(_attrname(attrname))
1459 newwidth = max(self.colwidths[attrname], len(text))
1592 newwidth = max(self.colwidths[attrname], len(text))
1460 self.colwidths[attrname] = newwidth
1593 self.colwidths[attrname] = newwidth
1461
1594
1462 # How many characters do we need to paint the item number?
1595 # How many characters do we need to paint the item number?
1463 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
1596 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
1464 # How must space have we got to display data?
1597 # How must space have we got to display data?
1465 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
1598 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
1466 # width of all columns
1599 # width of all columns
1467 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
1600 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
1468
1601
1469 def calcdisplayattr(self):
1602 def calcdisplayattr(self):
1470 # Find out on which attribute the cursor is on
1603 # Find out on which attribute the cursor is on and store this
1604 # information in ``self.displayattr``.
1471 pos = 0
1605 pos = 0
1472 for attrname in self.displayattrs:
1606 for attrname in self.displayattrs:
1473 if pos+self.colwidths[attrname] >= self.curx:
1607 if pos+self.colwidths[attrname] >= self.curx:
1474 self.displayattr = attrname
1608 self.displayattr = attrname
1475 break
1609 break
1476 pos += self.colwidths[attrname]+1
1610 pos += self.colwidths[attrname]+1
1477 else:
1611 else:
1478 self.displayattr = None
1612 self.displayattr = None
1479
1613
1480 def moveto(self, x, y, refresh=False):
1614 def moveto(self, x, y, refresh=False):
1615 # Move the cursor to the position ``(x,y)`` (in data coordinates,
1616 # not in screen coordinates). If ``refresh`` is true, all cached
1617 # values will be recalculated (e.g. because the list has been
1618 # resorted to screen position etc. are no longer valid).
1481 olddatastarty = self.datastarty
1619 olddatastarty = self.datastarty
1482 oldx = self.curx
1620 oldx = self.curx
1483 oldy = self.cury
1621 oldy = self.cury
1484 x = int(x+0.5)
1622 x = int(x+0.5)
1485 y = int(y+0.5)
1623 y = int(y+0.5)
1486
1624
1487 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
1625 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
1488 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
1626 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
1489
1627
1490 # Make sure that the cursor didn't leave the main area vertically
1628 # Make sure that the cursor didn't leave the main area vertically
1491 if y < 0:
1629 if y < 0:
1492 y = 0
1630 y = 0
1493 self.fetch(y+scrollbordery+1) # try to get more items
1631 self.fetch(y+scrollbordery+1) # try to get more items
1494 if y >= len(self.items):
1632 if y >= len(self.items):
1495 y = max(0, len(self.items)-1)
1633 y = max(0, len(self.items)-1)
1496
1634
1497 # Make sure that the cursor stays on screen vertically
1635 # Make sure that the cursor stays on screen vertically
1498 if y < self.datastarty+scrollbordery:
1636 if y < self.datastarty+scrollbordery:
1499 self.datastarty = max(0, y-scrollbordery)
1637 self.datastarty = max(0, y-scrollbordery)
1500 elif y >= self.datastarty+self.mainsizey-scrollbordery:
1638 elif y >= self.datastarty+self.mainsizey-scrollbordery:
1501 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
1639 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
1502 len(self.items)-self.mainsizey))
1640 len(self.items)-self.mainsizey))
1503
1641
1504 if refresh: # Do we need to refresh the complete display?
1642 if refresh: # Do we need to refresh the complete display?
1505 self.calcdisplayattrs()
1643 self.calcdisplayattrs()
1506 endy = min(self.datastarty+self.mainsizey, len(self.items))
1644 endy = min(self.datastarty+self.mainsizey, len(self.items))
1507 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
1645 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
1508 self.calcwidths()
1646 self.calcwidths()
1509
1510 # Did we scroll vertically => update displayrows
1647 # Did we scroll vertically => update displayrows
1511 # and various other attributes
1648 # and various other attributes
1512 elif self.datastarty != olddatastarty:
1649 elif self.datastarty != olddatastarty:
1513 # Recalculate which attributes we have to display
1650 # Recalculate which attributes we have to display
1514 olddisplayattrs = self.displayattrs
1651 olddisplayattrs = self.displayattrs
1515 self.calcdisplayattrs()
1652 self.calcdisplayattrs()
1516 # If there are new attributes, recreate the cache
1653 # If there are new attributes, recreate the cache
1517 if self.displayattrs != olddisplayattrs:
1654 if self.displayattrs != olddisplayattrs:
1518 endy = min(self.datastarty+self.mainsizey, len(self.items))
1655 endy = min(self.datastarty+self.mainsizey, len(self.items))
1519 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
1656 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
1520 elif self.datastarty<olddatastarty: # we did scroll up
1657 elif self.datastarty<olddatastarty: # we did scroll up
1521 # drop rows from the end
1658 # drop rows from the end
1522 del self.displayrows[self.datastarty-olddatastarty:]
1659 del self.displayrows[self.datastarty-olddatastarty:]
1523 # fetch new items
1660 # fetch new items
1524 for i in xrange(olddatastarty-1,
1661 for i in xrange(olddatastarty-1,
1525 self.datastarty-1, -1):
1662 self.datastarty-1, -1):
1526 try:
1663 try:
1527 row = self.getrow(i)
1664 row = self.getrow(i)
1528 except IndexError:
1665 except IndexError:
1529 # we didn't have enough objects to fill the screen
1666 # we didn't have enough objects to fill the screen
1530 break
1667 break
1531 self.displayrows.insert(0, row)
1668 self.displayrows.insert(0, row)
1532 else: # we did scroll down
1669 else: # we did scroll down
1533 # drop rows from the start
1670 # drop rows from the start
1534 del self.displayrows[:self.datastarty-olddatastarty]
1671 del self.displayrows[:self.datastarty-olddatastarty]
1535 # fetch new items
1672 # fetch new items
1536 for i in xrange(olddatastarty+self.mainsizey,
1673 for i in xrange(olddatastarty+self.mainsizey,
1537 self.datastarty+self.mainsizey):
1674 self.datastarty+self.mainsizey):
1538 try:
1675 try:
1539 row = self.getrow(i)
1676 row = self.getrow(i)
1540 except IndexError:
1677 except IndexError:
1541 # we didn't have enough objects to fill the screen
1678 # we didn't have enough objects to fill the screen
1542 break
1679 break
1543 self.displayrows.append(row)
1680 self.displayrows.append(row)
1544 self.calcwidths()
1681 self.calcwidths()
1545
1682
1546 # Make sure that the cursor didn't leave the data area horizontally
1683 # Make sure that the cursor didn't leave the data area horizontally
1547 if x < 0:
1684 if x < 0:
1548 x = 0
1685 x = 0
1549 elif x >= self.datasizex:
1686 elif x >= self.datasizex:
1550 x = max(0, self.datasizex-1)
1687 x = max(0, self.datasizex-1)
1551
1688
1552 # Make sure that the cursor stays on screen horizontally
1689 # Make sure that the cursor stays on screen horizontally
1553 if x < self.datastartx+scrollborderx:
1690 if x < self.datastartx+scrollborderx:
1554 self.datastartx = max(0, x-scrollborderx)
1691 self.datastartx = max(0, x-scrollborderx)
1555 elif x >= self.datastartx+self.mainsizex-scrollborderx:
1692 elif x >= self.datastartx+self.mainsizex-scrollborderx:
1556 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
1693 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
1557 self.datasizex-self.mainsizex))
1694 self.datasizex-self.mainsizex))
1558
1695
1559 if x == oldx and y == oldy: # couldn't move
1696 if x == oldx and y == oldy: # couldn't move
1560 if self.browser._dobeep:
1697 if self.browser._dobeep:
1561 curses.beep()
1698 curses.beep()
1562 # don't beep again (as long as the same key is pressed)
1699 # don't beep again (as long as the same key is pressed)
1563 self.browser._dobeep = False
1700 self.browser._dobeep = False
1564 else:
1701 else:
1565 self.curx = x
1702 self.curx = x
1566 self.cury = y
1703 self.cury = y
1567 self.calcdisplayattr()
1704 self.calcdisplayattr()
1568
1705
1569 def sort(self, key, reverse=False):
1706 def sort(self, key, reverse=False):
1570 """
1707 """
1571 Sort the currently list of items using the key function ``key``. If
1708 Sort the currently list of items using the key function ``key``. If
1572 ``reverse`` is true the sort order is reversed.
1709 ``reverse`` is true the sort order is reversed.
1573 """
1710 """
1574 curitem = self.items[self.cury] # Remember where the cursor is now
1711 curitem = self.items[self.cury] # Remember where the cursor is now
1575
1712
1576 # Sort items
1713 # Sort items
1577 def realkey(item):
1714 def realkey(item):
1578 return key(item.item)
1715 return key(item.item)
1579 self.items = deque(sorted(self.items, key=realkey, reverse=reverse))
1716 self.items = deque(sorted(self.items, key=realkey, reverse=reverse))
1580
1717
1581 # Find out where the object under the cursor went
1718 # Find out where the object under the cursor went
1582 cury = self.cury
1719 cury = self.cury
1583 for (i, item) in enumerate(self.items):
1720 for (i, item) in enumerate(self.items):
1584 if item is curitem:
1721 if item is curitem:
1585 cury = i
1722 cury = i
1586 break
1723 break
1587
1724
1588 self.moveto(self.curx, cury, refresh=True)
1725 self.moveto(self.curx, cury, refresh=True)
1589
1726
1590
1727
1591 class ibrowse(Display):
1728 class ibrowse(Display):
1592 # Show this many lines from the previous screen when paging horizontally
1729 # Show this many lines from the previous screen when paging horizontally
1593 pageoverlapx = 1
1730 pageoverlapx = 1
1594
1731
1595 # Show this many lines from the previous screen when paging vertically
1732 # Show this many lines from the previous screen when paging vertically
1596 pageoverlapy = 1
1733 pageoverlapy = 1
1597
1734
1598 # Start scrolling when the cursor is less than this number of columns
1735 # Start scrolling when the cursor is less than this number of columns
1599 # away from the left or right screen edge
1736 # away from the left or right screen edge
1600 scrollborderx = 10
1737 scrollborderx = 10
1601
1738
1602 # Start scrolling when the cursor is less than this number of lines
1739 # Start scrolling when the cursor is less than this number of lines
1603 # away from the top or bottom screen edge
1740 # away from the top or bottom screen edge
1604 scrollbordery = 5
1741 scrollbordery = 5
1605
1742
1606 # Accelerate by this factor when scrolling horizontally
1743 # Accelerate by this factor when scrolling horizontally
1607 acceleratex = 1.05
1744 acceleratex = 1.05
1608
1745
1609 # Accelerate by this factor when scrolling vertically
1746 # Accelerate by this factor when scrolling vertically
1610 acceleratey = 1.05
1747 acceleratey = 1.05
1611
1748
1612 # The maximum horizontal scroll speed
1749 # The maximum horizontal scroll speed
1613 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
1750 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
1614 maxspeedx = 0.5
1751 maxspeedx = 0.5
1615
1752
1616 # The maximum vertical scroll speed
1753 # The maximum vertical scroll speed
1617 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
1754 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
1618 maxspeedy = 0.5
1755 maxspeedy = 0.5
1619
1756
1620 # The maximum number of header lines for browser level
1757 # The maximum number of header lines for browser level
1621 # if the nesting is deeper, only the innermost levels are displayed
1758 # if the nesting is deeper, only the innermost levels are displayed
1622 maxheaders = 5
1759 maxheaders = 5
1623
1760
1624 # Styles for various parts of the GUI
1761 # Styles for various parts of the GUI
1625 style_objheadertext = Style(curses.COLOR_WHITE, curses.COLOR_BLACK, curses.A_BOLD|curses.A_REVERSE)
1762 style_objheadertext = Style(curses.COLOR_WHITE, curses.COLOR_BLACK, curses.A_BOLD|curses.A_REVERSE)
1626 style_objheadernumber = Style(curses.COLOR_WHITE, curses.COLOR_BLUE, curses.A_BOLD|curses.A_REVERSE)
1763 style_objheadernumber = Style(curses.COLOR_WHITE, curses.COLOR_BLUE, curses.A_BOLD|curses.A_REVERSE)
1627 style_objheaderobject = Style(curses.COLOR_WHITE, curses.COLOR_BLACK, curses.A_REVERSE)
1764 style_objheaderobject = Style(curses.COLOR_WHITE, curses.COLOR_BLACK, curses.A_REVERSE)
1628 style_colheader = Style(curses.COLOR_BLUE, curses.COLOR_WHITE, curses.A_REVERSE)
1765 style_colheader = Style(curses.COLOR_BLUE, curses.COLOR_WHITE, curses.A_REVERSE)
1629 style_colheaderhere = Style(curses.COLOR_GREEN, curses.COLOR_BLACK, curses.A_BOLD|curses.A_REVERSE)
1766 style_colheaderhere = Style(curses.COLOR_GREEN, curses.COLOR_BLACK, curses.A_BOLD|curses.A_REVERSE)
1630 style_colheadersep = Style(curses.COLOR_BLUE, curses.COLOR_BLACK, curses.A_REVERSE)
1767 style_colheadersep = Style(curses.COLOR_BLUE, curses.COLOR_BLACK, curses.A_REVERSE)
1631 style_number = Style(curses.COLOR_BLUE, curses.COLOR_WHITE, curses.A_REVERSE)
1768 style_number = Style(curses.COLOR_BLUE, curses.COLOR_WHITE, curses.A_REVERSE)
1632 style_numberhere = Style(curses.COLOR_GREEN, curses.COLOR_BLACK, curses.A_BOLD|curses.A_REVERSE)
1769 style_numberhere = Style(curses.COLOR_GREEN, curses.COLOR_BLACK, curses.A_BOLD|curses.A_REVERSE)
1633 style_sep = Style(curses.COLOR_BLUE, curses.COLOR_BLACK)
1770 style_sep = Style(curses.COLOR_BLUE, curses.COLOR_BLACK)
1634 style_data = Style(curses.COLOR_WHITE, curses.COLOR_BLACK)
1771 style_data = Style(curses.COLOR_WHITE, curses.COLOR_BLACK)
1635 style_datapad = Style(curses.COLOR_BLUE, curses.COLOR_BLACK, curses.A_BOLD)
1772 style_datapad = Style(curses.COLOR_BLUE, curses.COLOR_BLACK, curses.A_BOLD)
1636 style_footer = Style(curses.COLOR_BLACK, curses.COLOR_WHITE)
1773 style_footer = Style(curses.COLOR_BLACK, curses.COLOR_WHITE)
1637 style_noattr = Style(curses.COLOR_RED, curses.COLOR_BLACK)
1774 style_noattr = Style(curses.COLOR_RED, curses.COLOR_BLACK)
1638 style_error = Style(curses.COLOR_RED, curses.COLOR_BLACK)
1775 style_error = Style(curses.COLOR_RED, curses.COLOR_BLACK)
1639 style_default = Style(curses.COLOR_WHITE, curses.COLOR_BLACK)
1776 style_default = Style(curses.COLOR_WHITE, curses.COLOR_BLACK)
1640 style_report = Style(curses.COLOR_WHITE, curses.COLOR_BLACK)
1777 style_report = Style(curses.COLOR_WHITE, curses.COLOR_BLACK)
1641
1778
1642 # Styles for datatypes
1779 # Styles for datatypes
1643 style_type_none = Style(curses.COLOR_MAGENTA, curses.COLOR_BLACK)
1780 style_type_none = Style(curses.COLOR_MAGENTA, curses.COLOR_BLACK)
1644 style_type_bool = Style(curses.COLOR_GREEN, curses.COLOR_BLACK)
1781 style_type_bool = Style(curses.COLOR_GREEN, curses.COLOR_BLACK)
1645 style_type_number = Style(curses.COLOR_YELLOW, curses.COLOR_BLACK)
1782 style_type_number = Style(curses.COLOR_YELLOW, curses.COLOR_BLACK)
1646 style_type_datetime = Style(curses.COLOR_CYAN, curses.COLOR_BLACK)
1783 style_type_datetime = Style(curses.COLOR_CYAN, curses.COLOR_BLACK)
1647
1784
1648 # Column separator in header
1785 # Column separator in header
1649 headersepchar = "|"
1786 headersepchar = "|"
1650
1787
1651 # Character for padding data cell entries
1788 # Character for padding data cell entries
1652 datapadchar = "."
1789 datapadchar = "."
1653
1790
1654 # Column separator in data area
1791 # Column separator in data area
1655 datasepchar = "|"
1792 datasepchar = "|"
1656
1793
1657 # Character to use for "empty" cell (i.e. for non-existing attributes)
1794 # Character to use for "empty" cell (i.e. for non-existing attributes)
1658 nodatachar = "-"
1795 nodatachar = "-"
1659
1796
1660 # Maps curses key codes to "function" names
1797 # Maps curses key codes to "function" names
1661 keymap = {
1798 keymap = {
1662 ord("q"): "quit",
1799 ord("q"): "quit",
1663 curses.KEY_UP: "up",
1800 curses.KEY_UP: "up",
1664 curses.KEY_DOWN: "down",
1801 curses.KEY_DOWN: "down",
1665 curses.KEY_PPAGE: "pageup",
1802 curses.KEY_PPAGE: "pageup",
1666 curses.KEY_NPAGE: "pagedown",
1803 curses.KEY_NPAGE: "pagedown",
1667 curses.KEY_LEFT: "left",
1804 curses.KEY_LEFT: "left",
1668 curses.KEY_RIGHT: "right",
1805 curses.KEY_RIGHT: "right",
1669 curses.KEY_HOME: "home",
1806 curses.KEY_HOME: "home",
1670 curses.KEY_END: "end",
1807 curses.KEY_END: "end",
1671 ord("p"): "pick",
1808 ord("p"): "pick",
1672 ord("P"): "pickattr",
1809 ord("P"): "pickattr",
1673 ord("C"): "pickallattrs",
1810 ord("C"): "pickallattrs",
1674 ord("m"): "pickmarked",
1811 ord("m"): "pickmarked",
1675 ord("M"): "pickmarkedattr",
1812 ord("M"): "pickmarkedattr",
1676 ord("\n"): "enterdefault",
1813 ord("\n"): "enterdefault",
1677 # FIXME: What's happening here?
1814 # FIXME: What's happening here?
1678 8: "leave",
1815 8: "leave",
1679 127: "leave",
1816 127: "leave",
1680 curses.KEY_BACKSPACE: "leave",
1817 curses.KEY_BACKSPACE: "leave",
1681 ord("x"): "leave",
1818 ord("x"): "leave",
1682 ord("h"): "help",
1819 ord("h"): "help",
1683 ord("e"): "enter",
1820 ord("e"): "enter",
1684 ord("E"): "enterattr",
1821 ord("E"): "enterattr",
1685 ord("d"): "detail",
1822 ord("d"): "detail",
1686 ord(" "): "tooglemark",
1823 ord(" "): "tooglemark",
1687 ord("r"): "markrange",
1824 ord("r"): "markrange",
1688 ord("v"): "sortattrasc",
1825 ord("v"): "sortattrasc",
1689 ord("V"): "sortattrdesc",
1826 ord("V"): "sortattrdesc",
1690 }
1827 }
1691
1828
1692 def __init__(self, *attrs):
1829 def __init__(self, *attrs):
1830 """
1831 Create a new browser. If ``attrs`` is not empty, it is the list
1832 of attributes that will be displayed in the browser, otherwise
1833 these will be determined by the objects on screen.
1834 """
1693 self.attrs = attrs
1835 self.attrs = attrs
1836
1837 # Stack of browser levels
1694 self.levels = []
1838 self.levels = []
1695 self.stepx = 1. # how many colums to scroll
1839 # how many colums to scroll (Changes when accelerating)
1696 self.stepy = 1. # how many rows to scroll
1840 self.stepx = 1.
1697 self._dobeep = True # Beep on the edges of the data area?
1841
1842 # how many rows to scroll (Changes when accelerating)
1843 self.stepy = 1.
1844
1845 # Beep on the edges of the data area? (Will be set to ``False``
1846 # once the cursor hits the edge of the screen, so we don't get
1847 # multiple beeps).
1848 self._dobeep = True
1849
1850 # Cache for registered ``curses`` colors.
1698 self._colors = {}
1851 self._colors = {}
1699 self._maxcolor = 1
1852 self._maxcolor = 1
1700 self._headerlines = 1 # How many header lines do we want to paint (the numbers of levels we have, but with an upper bound)
1853
1701 self._firstheaderline = 0 # Index of first header line
1854 # How many header lines do we want to paint (the numbers of levels
1702 self.scr = None # curses window
1855 # we have, but with an upper bound)
1703 self._report = None # report in the footer line
1856 self._headerlines = 1
1857
1858 # Index of first header line
1859 self._firstheaderline = 0
1860
1861 # curses window
1862 self.scr = None
1863 # report in the footer line (error, executed command etc.)
1864 self._report = None
1704
1865
1705 def nextstepx(self, step):
1866 def nextstepx(self, step):
1867 """
1868 Accelerate horizontally.
1869 """
1706 return max(1., min(step*self.acceleratex,
1870 return max(1., min(step*self.acceleratex,
1707 self.maxspeedx*self.levels[-1].mainsizex))
1871 self.maxspeedx*self.levels[-1].mainsizex))
1708
1872
1709 def nextstepy(self, step):
1873 def nextstepy(self, step):
1874 """
1875 Accelerate vertically.
1876 """
1710 return max(1., min(step*self.acceleratey,
1877 return max(1., min(step*self.acceleratey,
1711 self.maxspeedy*self.levels[-1].mainsizey))
1878 self.maxspeedy*self.levels[-1].mainsizey))
1712
1879
1713 def getstyle(self, style):
1880 def getstyle(self, style):
1881 """
1882 Register the ``style`` with ``curses`` or get it from the cache,
1883 if it has been registered before.
1884 """
1714 try:
1885 try:
1715 return self._colors[style.fg, style.bg] | style.attrs
1886 return self._colors[style.fg, style.bg] | style.attrs
1716 except KeyError:
1887 except KeyError:
1717 curses.init_pair(self._maxcolor, style.fg, style.bg)
1888 curses.init_pair(self._maxcolor, style.fg, style.bg)
1718 pair = curses.color_pair(self._maxcolor)
1889 pair = curses.color_pair(self._maxcolor)
1719 self._colors[style.fg, style.bg] = pair
1890 self._colors[style.fg, style.bg] = pair
1720 c = pair | style.attrs
1891 c = pair | style.attrs
1721 self._maxcolor += 1
1892 self._maxcolor += 1
1722 return c
1893 return c
1723
1894
1724 def addstr(self, y, x, begx, endx, s, style):
1895 def addstr(self, y, x, begx, endx, s, style):
1896 """
1897 A version of ``curses.addstr()`` that can handle ``x`` coordinates
1898 that are outside the screen.
1899 """
1725 s2 = s[max(0, begx-x):max(0, endx-x)]
1900 s2 = s[max(0, begx-x):max(0, endx-x)]
1726 if s2:
1901 if s2:
1727 self.scr.addstr(y, max(x, begx), s2, self.getstyle(style))
1902 self.scr.addstr(y, max(x, begx), s2, self.getstyle(style))
1728 return len(s)
1903 return len(s)
1729
1904
1730 def format(self, value):
1905 def format(self, value):
1906 """
1907 Formats one attribute and returns an ``(alignment, string, style)``
1908 tuple.
1909 """
1731 if value is None:
1910 if value is None:
1732 return (-1, repr(value), self.style_type_none)
1911 return (-1, repr(value), self.style_type_none)
1733 elif isinstance(value, str):
1912 elif isinstance(value, str):
1734 return (-1, repr(value.expandtabs(tab))[1:-1], self.style_default)
1913 return (-1, repr(value.expandtabs(tab))[1:-1], self.style_default)
1735 elif isinstance(value, unicode):
1914 elif isinstance(value, unicode):
1736 return (-1, repr(value.expandtabs(tab))[2:-1], self.style_default)
1915 return (-1, repr(value.expandtabs(tab))[2:-1], self.style_default)
1737 elif isinstance(value, datetime.datetime):
1916 elif isinstance(value, datetime.datetime):
1738 # Don't use strftime() here, as this requires year >= 1900
1917 # Don't use strftime() here, as this requires year >= 1900
1739 return (-1, "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
1918 return (-1, "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
1740 (value.year, value.month, value.day,
1919 (value.year, value.month, value.day,
1741 value.hour, value.minute, value.second,
1920 value.hour, value.minute, value.second,
1742 value.microsecond),
1921 value.microsecond),
1743 self.style_type_datetime)
1922 self.style_type_datetime)
1744 elif isinstance(value, datetime.date):
1923 elif isinstance(value, datetime.date):
1745 return (-1, "%04d-%02d-%02d" % \
1924 return (-1, "%04d-%02d-%02d" % \
1746 (value.year, value.month, value.day),
1925 (value.year, value.month, value.day),
1747 self.style_type_datetime)
1926 self.style_type_datetime)
1748 elif isinstance(value, datetime.time):
1927 elif isinstance(value, datetime.time):
1749 return (-1, "%02d:%02d:%02d.%06d" % \
1928 return (-1, "%02d:%02d:%02d.%06d" % \
1750 (value.hour, value.minute, value.second,
1929 (value.hour, value.minute, value.second,
1751 value.microsecond),
1930 value.microsecond),
1752 self.style_type_datetime)
1931 self.style_type_datetime)
1753 elif isinstance(value, datetime.timedelta):
1932 elif isinstance(value, datetime.timedelta):
1754 return (-1, repr(value), self.style_type_datetime)
1933 return (-1, repr(value), self.style_type_datetime)
1755 elif isinstance(value, bool):
1934 elif isinstance(value, bool):
1756 return (-1, repr(value), self.style_type_bool)
1935 return (-1, repr(value), self.style_type_bool)
1757 elif isinstance(value, (int, long, float)):
1936 elif isinstance(value, (int, long, float)):
1758 return (1, repr(value), self.style_type_number)
1937 return (1, repr(value), self.style_type_number)
1759 elif isinstance(value, complex):
1938 elif isinstance(value, complex):
1760 return (-1, repr(value), self.style_type_number)
1939 return (-1, repr(value), self.style_type_number)
1761 elif isinstance(value, Exception):
1940 elif isinstance(value, Exception):
1762 if value.__class__.__module__ == "exceptions":
1941 if value.__class__.__module__ == "exceptions":
1763 value = "%s: %s" % (value.__class__.__name__, value)
1942 value = "%s: %s" % (value.__class__.__name__, value)
1764 else:
1943 else:
1765 value = "%s.%s: %s" % \
1944 value = "%s.%s: %s" % \
1766 (value.__class__.__module__, value.__class__.__name__,
1945 (value.__class__.__module__, value.__class__.__name__,
1767 value)
1946 value)
1768 return (-1, value, self.style_error)
1947 return (-1, value, self.style_error)
1769 return (-1, repr(value), self.style_default)
1948 return (-1, repr(value), self.style_default)
1770
1949
1771 def _calcheaderlines(self, levels):
1950 def _calcheaderlines(self, levels):
1951 # Calculate how many headerlines do we have to display, if we have
1952 # ``levels`` browser levels
1772 if levels is None:
1953 if levels is None:
1773 levels = len(self.levels)
1954 levels = len(self.levels)
1774 self._headerlines = min(self.maxheaders, levels)
1955 self._headerlines = min(self.maxheaders, levels)
1775 self._firstheaderline = levels-self._headerlines
1956 self._firstheaderline = levels-self._headerlines
1776
1957
1777 def getstylehere(self, style):
1958 def getstylehere(self, style):
1778 """
1959 """
1779 Return a style for displaying the original style ``style``
1960 Return a style for displaying the original style ``style``
1780 in the row the cursor is on.
1961 in the row the cursor is on.
1781 """
1962 """
1782 return Style(style.fg, style.bg, style.attrs | curses.A_BOLD)
1963 return Style(style.fg, style.bg, style.attrs | curses.A_BOLD)
1783
1964
1784 def report(self, msg):
1965 def report(self, msg):
1966 """
1967 Store the message ``msg`` for display below the footer line. This
1968 will be displayed as soon as the screen is redrawn.
1969 """
1785 self._report = msg
1970 self._report = msg
1786
1971
1787 def enter(self, item, mode, *attrs):
1972 def enter(self, item, mode, *attrs):
1973 """
1974 Enter the object ``item`` in the mode ``mode``. If ``attrs`` is
1975 specified, it will be used as a fixed list of attributes to display.
1976 """
1788 try:
1977 try:
1789 iterator = xiter(item, mode)
1978 iterator = xiter(item, mode)
1790 except (KeyboardInterrupt, SystemExit):
1979 except (KeyboardInterrupt, SystemExit):
1791 raise
1980 raise
1792 except Exception, exc:
1981 except Exception, exc:
1793 curses.beep()
1982 curses.beep()
1794 self.report(exc)
1983 self.report(exc)
1795 else:
1984 else:
1796 self._calcheaderlines(len(self.levels)+1)
1985 self._calcheaderlines(len(self.levels)+1)
1797 level = _BrowserLevel(
1986 level = _BrowserLevel(
1798 self,
1987 self,
1799 item,
1988 item,
1800 iterator,
1989 iterator,
1801 self.scrsizey-1-self._headerlines-2,
1990 self.scrsizey-1-self._headerlines-2,
1802 *attrs
1991 *attrs
1803 )
1992 )
1804 self.levels.append(level)
1993 self.levels.append(level)
1805
1994
1806 def keylabel(self, keycode):
1995 def keylabel(self, keycode):
1996 """
1997 Return a pretty name for the ``curses`` key ``keycode`` (used in the
1998 help screen and in reports about unassigned keys).
1999 """
1807 if keycode <= 0xff:
2000 if keycode <= 0xff:
1808 specialsnames = {
2001 specialsnames = {
1809 ord("\n"): "RETURN",
2002 ord("\n"): "RETURN",
1810 ord(" "): "SPACE",
2003 ord(" "): "SPACE",
1811 ord("\t"): "TAB",
2004 ord("\t"): "TAB",
1812 ord("\x7f"): "DELETE",
2005 ord("\x7f"): "DELETE",
1813 ord("\x08"): "BACKSPACE",
2006 ord("\x08"): "BACKSPACE",
1814 }
2007 }
1815 if keycode in specialsnames:
2008 if keycode in specialsnames:
1816 return specialsnames[keycode]
2009 return specialsnames[keycode]
1817 return repr(chr(keycode))
2010 return repr(chr(keycode))
1818 for name in dir(curses):
2011 for name in dir(curses):
1819 if name.startswith("KEY_") and getattr(curses, name) == keycode:
2012 if name.startswith("KEY_") and getattr(curses, name) == keycode:
1820 return name
2013 return name
1821 return str(keycode)
2014 return str(keycode)
1822
2015
1823 def cmd_help(self):
2016 def cmd_help(self):
2017 """
2018 The help command
2019 """
1824 for level in self.levels:
2020 for level in self.levels:
1825 if isinstance(level.input, _BrowserHelp):
2021 if isinstance(level.input, _BrowserHelp):
1826 curses.beep()
2022 curses.beep()
1827 self.report(CommandError("help already active"))
2023 self.report(CommandError("help already active"))
1828 return
2024 return
1829
2025
1830 self.enter(_BrowserHelp(self), "default")
2026 self.enter(_BrowserHelp(self), "default")
1831
2027
1832 def _dodisplay(self, scr):
2028 def _dodisplay(self, scr):
2029 """
2030 This method is the workhorse of the browser. It handles screen
2031 drawing and the keyboard.
2032 """
1833 self.scr = scr
2033 self.scr = scr
1834 curses.halfdelay(1)
2034 curses.halfdelay(1)
1835 footery = 2
2035 footery = 2
1836
2036
1837 keys = []
2037 keys = []
1838 for (key, cmd) in self.keymap.iteritems():
2038 for (key, cmd) in self.keymap.iteritems():
1839 if cmd == "quit":
2039 if cmd == "quit":
1840 keys.append("%s=%s" % (self.keylabel(key), cmd))
2040 keys.append("%s=%s" % (self.keylabel(key), cmd))
1841 for (key, cmd) in self.keymap.iteritems():
2041 for (key, cmd) in self.keymap.iteritems():
1842 if cmd == "help":
2042 if cmd == "help":
1843 keys.append("%s=%s" % (self.keylabel(key), cmd))
2043 keys.append("%s=%s" % (self.keylabel(key), cmd))
1844 helpmsg = " %s" % " ".join(keys)
2044 helpmsg = " %s" % " ".join(keys)
1845
2045
1846 scr.clear()
2046 scr.clear()
1847 msg = "Fetching first batch of objects..."
2047 msg = "Fetching first batch of objects..."
1848 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
2048 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1849 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
2049 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1850 scr.refresh()
2050 scr.refresh()
1851
2051
1852 lastc = -1
2052 lastc = -1
1853
2053
1854 self.levels = []
2054 self.levels = []
1855 # enter the first level
2055 # enter the first level
1856 self.enter(self.input, xiter(self.input, "default"), *self.attrs)
2056 self.enter(self.input, xiter(self.input, "default"), *self.attrs)
1857
2057
1858 self._calcheaderlines(None)
2058 self._calcheaderlines(None)
1859
2059
1860 while True:
2060 while True:
1861 level = self.levels[-1]
2061 level = self.levels[-1]
1862 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
2062 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1863 level.mainsizey = self.scrsizey-1-self._headerlines-footery
2063 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1864
2064
1865 # Paint object header
2065 # Paint object header
1866 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
2066 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1867 lv = self.levels[i]
2067 lv = self.levels[i]
1868 posx = 0
2068 posx = 0
1869 posx += self.addstr(i-self._firstheaderline, posx, 0, self.scrsizex, " ibrowse #%d: " % i, self.style_objheadertext)
2069 posx += self.addstr(i-self._firstheaderline, posx, 0, self.scrsizex, " ibrowse #%d: " % i, self.style_objheadertext)
1870 posx += self.addstr(i-self._firstheaderline, posx, 0, self.scrsizex, lv.header, self.style_objheaderobject)
2070 posx += self.addstr(i-self._firstheaderline, posx, 0, self.scrsizex, lv.header, self.style_objheaderobject)
1871 if i: # not the first level
2071 if i: # not the first level
1872 posx += self.addstr(i-self._firstheaderline, posx, 0, self.scrsizex, " == ", self.style_objheadertext)
2072 posx += self.addstr(i-self._firstheaderline, posx, 0, self.scrsizex, " == ", self.style_objheadertext)
1873 msg = "%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
2073 msg = "%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1874 if not self.levels[i-1].exhausted:
2074 if not self.levels[i-1].exhausted:
1875 msg += "+"
2075 msg += "+"
1876 posx += self.addstr(i-self._firstheaderline, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
2076 posx += self.addstr(i-self._firstheaderline, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1877 if posx < self.scrsizex:
2077 if posx < self.scrsizex:
1878 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_objheadertext))
2078 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_objheadertext))
1879
2079
1880 # Paint column headers
2080 # Paint column headers
1881 scr.move(self._headerlines, 0)
2081 scr.move(self._headerlines, 0)
1882 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
2082 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1883 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
2083 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1884 begx = level.numbersizex+3
2084 begx = level.numbersizex+3
1885 posx = begx-level.datastartx
2085 posx = begx-level.datastartx
1886 for attrname in level.displayattrs:
2086 for attrname in level.displayattrs:
1887 strattrname = _attrname(attrname)
2087 strattrname = _attrname(attrname)
1888 cwidth = level.colwidths[attrname]
2088 cwidth = level.colwidths[attrname]
1889 header = strattrname.ljust(cwidth)
2089 header = strattrname.ljust(cwidth)
1890 if attrname == level.displayattr:
2090 if attrname == level.displayattr:
1891 style = self.style_colheaderhere
2091 style = self.style_colheaderhere
1892 else:
2092 else:
1893 style = self.style_colheader
2093 style = self.style_colheader
1894 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
2094 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1895 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
2095 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1896 if posx >= self.scrsizex:
2096 if posx >= self.scrsizex:
1897 break
2097 break
1898 else:
2098 else:
1899 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
2099 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1900
2100
1901 # Paint rows
2101 # Paint rows
1902 posy = self._headerlines+1+level.datastarty
2102 posy = self._headerlines+1+level.datastarty
1903 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
2103 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1904 cache = level.items[i]
2104 cache = level.items[i]
1905 if i == level.cury:
2105 if i == level.cury:
1906 style = self.style_numberhere
2106 style = self.style_numberhere
1907 else:
2107 else:
1908 style = self.style_number
2108 style = self.style_number
1909
2109
1910 posy = self._headerlines+1+i-level.datastarty
2110 posy = self._headerlines+1+i-level.datastarty
1911 posx = begx-level.datastartx
2111 posx = begx-level.datastartx
1912
2112
1913 scr.move(posy, 0)
2113 scr.move(posy, 0)
1914 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
2114 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1915 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
2115 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1916
2116
1917 for attrname in level.displayattrs:
2117 for attrname in level.displayattrs:
1918 cwidth = level.colwidths[attrname]
2118 cwidth = level.colwidths[attrname]
1919 (align, text, style) = level.displayrows[i-level.datastarty].get(attrname, (2, None, self.style_noattr))
2119 (align, text, style) = level.displayrows[i-level.datastarty].get(attrname, (2, None, self.style_noattr))
1920 padstyle = self.style_datapad
2120 padstyle = self.style_datapad
1921 sepstyle = self.style_sep
2121 sepstyle = self.style_sep
1922 if i == level.cury:
2122 if i == level.cury:
1923 style = self.getstylehere(style)
2123 style = self.getstylehere(style)
1924 padstyle = self.getstylehere(padstyle)
2124 padstyle = self.getstylehere(padstyle)
1925 sepstyle = self.getstylehere(sepstyle)
2125 sepstyle = self.getstylehere(sepstyle)
1926 if align == 2:
2126 if align == 2:
1927 text = self.nodatachar*cwidth
2127 text = self.nodatachar*cwidth
1928 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
2128 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1929 elif align == -1:
2129 elif align == -1:
1930 pad = self.datapadchar*(cwidth-len(text))
2130 pad = self.datapadchar*(cwidth-len(text))
1931 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
2131 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1932 posx += self.addstr(posy, posx, begx, self.scrsizex, pad, padstyle)
2132 posx += self.addstr(posy, posx, begx, self.scrsizex, pad, padstyle)
1933 elif align == 0:
2133 elif align == 0:
1934 pad1 = self.datapadchar*((cwidth-len(text))//2)
2134 pad1 = self.datapadchar*((cwidth-len(text))//2)
1935 pad2 = self.datapadchar*(cwidth-len(text)-len(pad1))
2135 pad2 = self.datapadchar*(cwidth-len(text)-len(pad1))
1936 posx += self.addstr(posy, posx, begx, self.scrsizex, pad1, padstyle)
2136 posx += self.addstr(posy, posx, begx, self.scrsizex, pad1, padstyle)
1937 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
2137 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1938 posx += self.addstr(posy, posx, begx, self.scrsizex, pad2, padstyle)
2138 posx += self.addstr(posy, posx, begx, self.scrsizex, pad2, padstyle)
1939 elif align == 1:
2139 elif align == 1:
1940 pad = self.datapadchar*(cwidth-len(text))
2140 pad = self.datapadchar*(cwidth-len(text))
1941 posx += self.addstr(posy, posx, begx, self.scrsizex, pad, padstyle)
2141 posx += self.addstr(posy, posx, begx, self.scrsizex, pad, padstyle)
1942 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
2142 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1943 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
2143 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1944 if posx >= self.scrsizex:
2144 if posx >= self.scrsizex:
1945 break
2145 break
1946 else:
2146 else:
1947 scr.clrtoeol()
2147 scr.clrtoeol()
1948
2148
1949 # Add blank row headers for the rest of the screen
2149 # Add blank row headers for the rest of the screen
1950 for posy in xrange(posy+1, self.scrsizey-2):
2150 for posy in xrange(posy+1, self.scrsizey-2):
1951 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
2151 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1952 scr.clrtoeol()
2152 scr.clrtoeol()
1953
2153
1954 # Display footer
2154 # Display footer
1955 scr.addstr(self.scrsizey-footery, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
2155 scr.addstr(self.scrsizey-footery, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1956
2156
1957 if level.exhausted:
2157 if level.exhausted:
1958 flag = ""
2158 flag = ""
1959 else:
2159 else:
1960 flag = "+"
2160 flag = "+"
1961
2161
1962 scr.addstr(self.scrsizey-footery, self.scrsizex-len(helpmsg)-1, helpmsg, self.getstyle(self.style_footer))
2162 scr.addstr(self.scrsizey-footery, self.scrsizex-len(helpmsg)-1, helpmsg, self.getstyle(self.style_footer))
1963
2163
1964 msg = "%d%s objects (%d marked)" % (len(level.items), flag, level.marked)
2164 msg = "%d%s objects (%d marked)" % (len(level.items), flag, level.marked)
1965 try:
2165 try:
1966 msg += ": %s > %s" % (xrepr(level.items[level.cury].item, "footer"), _attrname(level.displayattr))
2166 msg += ": %s > %s" % (xrepr(level.items[level.cury].item, "footer"), _attrname(level.displayattr))
1967 except IndexError: # empty
2167 except IndexError: # empty
1968 pass
2168 pass
1969 self.addstr(self.scrsizey-footery, 1, 1, self.scrsizex-len(helpmsg)-1, msg, self.style_footer)
2169 self.addstr(self.scrsizey-footery, 1, 1, self.scrsizex-len(helpmsg)-1, msg, self.style_footer)
1970
2170
1971 # Display report
2171 # Display report
1972 if self._report is not None:
2172 if self._report is not None:
1973 if isinstance(self._report, Exception):
2173 if isinstance(self._report, Exception):
1974 style = self.getstyle(self.style_error)
2174 style = self.getstyle(self.style_error)
1975 if self._report.__class__.__module__ == "exceptions":
2175 if self._report.__class__.__module__ == "exceptions":
1976 msg = "%s: %s" % \
2176 msg = "%s: %s" % \
1977 (self._report.__class__.__name__, self._report)
2177 (self._report.__class__.__name__, self._report)
1978 else:
2178 else:
1979 msg = "%s.%s: %s" % \
2179 msg = "%s.%s: %s" % \
1980 (self._report.__class__.__module__,
2180 (self._report.__class__.__module__,
1981 self._report.__class__.__name__, self._report)
2181 self._report.__class__.__name__, self._report)
1982 else:
2182 else:
1983 style = self.getstyle(self.style_report)
2183 style = self.getstyle(self.style_report)
1984 msg = self._report
2184 msg = self._report
1985 try:
2185 try:
1986 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
2186 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1987 except curses.err:
2187 except curses.err:
1988 # Protect against error from writing to the last line
2188 # Protect against error from writing to the last line
1989 pass
2189 pass
1990 self._report = None
2190 self._report = None
1991 else:
2191 else:
1992 scr.move(self.scrsizey-1, 0)
2192 scr.move(self.scrsizey-1, 0)
1993 scr.clrtoeol()
2193 scr.clrtoeol()
1994
2194
1995 # Position cursor
2195 # Position cursor
1996 scr.move(
2196 scr.move(
1997 1+self._headerlines+level.cury-level.datastarty,
2197 1+self._headerlines+level.cury-level.datastarty,
1998 level.numbersizex+3+level.curx-level.datastartx
2198 level.numbersizex+3+level.curx-level.datastartx
1999 )
2199 )
2000 scr.refresh()
2200 scr.refresh()
2001
2201
2002 # Check keyboard
2202 # Check keyboard
2003 while True:
2203 while True:
2004 c = scr.getch()
2204 c = scr.getch()
2005 # if no key is pressed slow down and beep again
2205 # if no key is pressed slow down and beep again
2006 if c == -1:
2206 if c == -1:
2007 self.stepx = 1.
2207 self.stepx = 1.
2008 self.stepy = 1.
2208 self.stepy = 1.
2009 self._dobeep = True
2209 self._dobeep = True
2010 else:
2210 else:
2011 # if a different key was pressed slow down and beep too
2211 # if a different key was pressed slow down and beep too
2012 if c != lastc:
2212 if c != lastc:
2013 lastc = c
2213 lastc = c
2014 self.stepx = 1.
2214 self.stepx = 1.
2015 self.stepy = 1.
2215 self.stepy = 1.
2016 self._dobeep = True
2216 self._dobeep = True
2017 cmd = self.keymap.get(c, None)
2217 cmd = self.keymap.get(c, None)
2018 if cmd == "quit":
2218 if cmd == "quit":
2019 return
2219 return
2020 elif cmd == "up":
2220 elif cmd == "up":
2021 self.report("up")
2221 self.report("up")
2022 level.moveto(level.curx, level.cury-self.stepy)
2222 level.moveto(level.curx, level.cury-self.stepy)
2023 elif cmd == "down":
2223 elif cmd == "down":
2024 self.report("down")
2224 self.report("down")
2025 level.moveto(level.curx, level.cury+self.stepy)
2225 level.moveto(level.curx, level.cury+self.stepy)
2026 elif cmd == "pageup":
2226 elif cmd == "pageup":
2027 self.report("page up")
2227 self.report("page up")
2028 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
2228 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
2029 elif cmd == "pagedown":
2229 elif cmd == "pagedown":
2030 self.report("page down")
2230 self.report("page down")
2031 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
2231 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
2032 elif cmd == "left":
2232 elif cmd == "left":
2033 self.report("left")
2233 self.report("left")
2034 level.moveto(level.curx-self.stepx, level.cury)
2234 level.moveto(level.curx-self.stepx, level.cury)
2035 elif cmd == "right":
2235 elif cmd == "right":
2036 self.report("right")
2236 self.report("right")
2037 level.moveto(level.curx+self.stepx, level.cury)
2237 level.moveto(level.curx+self.stepx, level.cury)
2038 elif cmd == "home":
2238 elif cmd == "home":
2039 self.report("home")
2239 self.report("home")
2040 level.moveto(0, level.cury)
2240 level.moveto(0, level.cury)
2041 elif cmd == "end":
2241 elif cmd == "end":
2042 self.report("end")
2242 self.report("end")
2043 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
2243 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
2044 elif cmd == "pick":
2244 elif cmd == "pick":
2045 return level.items[level.cury].item
2245 return level.items[level.cury].item
2046 elif cmd == "pickattr":
2246 elif cmd == "pickattr":
2047 attr = _getattr(level.items[level.cury].item, level.displayattr)
2247 attr = _getattr(level.items[level.cury].item, level.displayattr)
2048 if attr is _default:
2248 if attr is _default:
2049 curses.beep()
2249 curses.beep()
2050 self.report(AttributeError(_attrname(level.displayattr)))
2250 self.report(AttributeError(_attrname(level.displayattr)))
2051 else:
2251 else:
2052 return attr
2252 return attr
2053 elif cmd == "pickallattrs":
2253 elif cmd == "pickallattrs":
2054 result = []
2254 result = []
2055 for cache in level.items:
2255 for cache in level.items:
2056 attr = _getattr(cache.item, level.displayattr)
2256 attr = _getattr(cache.item, level.displayattr)
2057 if attr is not _default:
2257 if attr is not _default:
2058 result.append(attr)
2258 result.append(attr)
2059 return result
2259 return result
2060 elif cmd == "pickmarked":
2260 elif cmd == "pickmarked":
2061 return [cache.item for cache in level.items if cache.marked]
2261 return [cache.item for cache in level.items if cache.marked]
2062 elif cmd == "pickmarkedattr":
2262 elif cmd == "pickmarkedattr":
2063 result = []
2263 result = []
2064 for cache in level.items:
2264 for cache in level.items:
2065 if cache.marked:
2265 if cache.marked:
2066 attr = _getattr(cache.item, level.displayattr)
2266 attr = _getattr(cache.item, level.displayattr)
2067 if attr is not _default:
2267 if attr is not _default:
2068 result.append(attr)
2268 result.append(attr)
2069 return result
2269 return result
2070 elif cmd == "markrange":
2270 elif cmd == "markrange":
2071 self.report("markrange")
2271 self.report("markrange")
2072 start = None
2272 start = None
2073 if level.items:
2273 if level.items:
2074 for i in xrange(level.cury, -1, -1):
2274 for i in xrange(level.cury, -1, -1):
2075 if level.items[i].marked:
2275 if level.items[i].marked:
2076 start = i
2276 start = i
2077 break
2277 break
2078 if start is None:
2278 if start is None:
2079 self.report(CommandError("no mark before cursor"))
2279 self.report(CommandError("no mark before cursor"))
2080 curses.beep()
2280 curses.beep()
2081 else:
2281 else:
2082 for i in xrange(start, level.cury+1):
2282 for i in xrange(start, level.cury+1):
2083 cache = level.items[i]
2283 cache = level.items[i]
2084 if not cache.marked:
2284 if not cache.marked:
2085 cache.marked = True
2285 cache.marked = True
2086 level.marked += 1
2286 level.marked += 1
2087 elif cmd == "enterdefault":
2287 elif cmd == "enterdefault":
2088 self.report("entering object (default mode)...")
2288 self.report("entering object (default mode)...")
2089 self.enter(level.items[level.cury].item, "default")
2289 self.enter(level.items[level.cury].item, "default")
2090 elif cmd == "leave":
2290 elif cmd == "leave":
2091 self.report("leave")
2291 self.report("leave")
2092 if len(self.levels) > 1:
2292 if len(self.levels) > 1:
2093 self._calcheaderlines(len(self.levels)-1)
2293 self._calcheaderlines(len(self.levels)-1)
2094 self.levels.pop(-1)
2294 self.levels.pop(-1)
2095 else:
2295 else:
2096 self.report(CommandError("this is the last level"))
2296 self.report(CommandError("this is the last level"))
2097 curses.beep()
2297 curses.beep()
2098 elif cmd == "enter":
2298 elif cmd == "enter":
2099 self.report("entering object...")
2299 self.report("entering object...")
2100 self.enter(level.items[level.cury].item, None)
2300 self.enter(level.items[level.cury].item, None)
2101 elif cmd == "enterattr":
2301 elif cmd == "enterattr":
2102 self.report("entering object attribute %s..." % _attrname(level.displayattr))
2302 self.report("entering object attribute %s..." % _attrname(level.displayattr))
2103 self.enter(_getattr(level.items[level.cury].item, level.displayattr), None)
2303 self.enter(_getattr(level.items[level.cury].item, level.displayattr), None)
2104 elif cmd == "detail":
2304 elif cmd == "detail":
2105 self.enter(level.items[level.cury].item, "detail")
2305 self.enter(level.items[level.cury].item, "detail")
2106 elif cmd == "tooglemark":
2306 elif cmd == "tooglemark":
2107 self.report("toggle mark")
2307 self.report("toggle mark")
2108 try:
2308 try:
2109 item = level.items[level.cury]
2309 item = level.items[level.cury]
2110 except IndexError: # no items?
2310 except IndexError: # no items?
2111 pass
2311 pass
2112 else:
2312 else:
2113 if item.marked:
2313 if item.marked:
2114 item.marked = False
2314 item.marked = False
2115 level.marked -= 1
2315 level.marked -= 1
2116 else:
2316 else:
2117 item.marked = True
2317 item.marked = True
2118 level.marked += 1
2318 level.marked += 1
2119 elif cmd == "sortattrasc":
2319 elif cmd == "sortattrasc":
2120 self.report("sort by %s (ascending)" % _attrname(level.displayattr))
2320 self.report("sort by %s (ascending)" % _attrname(level.displayattr))
2121 def key(item):
2321 def key(item):
2122 return _getattr(item, level.displayattr)
2322 return _getattr(item, level.displayattr)
2123 level.sort(key)
2323 level.sort(key)
2124 elif cmd == "sortattrdesc":
2324 elif cmd == "sortattrdesc":
2125 self.report("sort by %s (descending)" % _attrname(level.displayattr))
2325 self.report("sort by %s (descending)" % _attrname(level.displayattr))
2126 def key(item):
2326 def key(item):
2127 return _getattr(item, level.displayattr)
2327 return _getattr(item, level.displayattr)
2128 level.sort(key, reverse=True)
2328 level.sort(key, reverse=True)
2129 elif cmd == "help":
2329 elif cmd == "help":
2130 self.cmd_help()
2330 self.cmd_help()
2131 elif cmd is not None:
2331 elif cmd is not None:
2132 self.report(UnknownCommandError("Unknown command %r" % (cmd,)))
2332 self.report(UnknownCommandError("Unknown command %r" % (cmd,)))
2133 else:
2333 else:
2134 self.report(UnassignedKeyError("Unassigned key %s" % self.keylabel(c)))
2334 self.report(UnassignedKeyError("Unassigned key %s" % self.keylabel(c)))
2135 self.stepx = self.nextstepx(self.stepx)
2335 self.stepx = self.nextstepx(self.stepx)
2136 self.stepy = self.nextstepy(self.stepy)
2336 self.stepy = self.nextstepy(self.stepy)
2137 curses.flushinp() # get rid of type ahead
2337 curses.flushinp() # get rid of type ahead
2138 break # Redisplay
2338 break # Redisplay
2139 self.scr = None
2339 self.scr = None
2140
2340
2141 def display(self):
2341 def display(self):
2142 return curses.wrapper(self._dodisplay)
2342 return curses.wrapper(self._dodisplay)
2143
2343
2144 defaultdisplay = ibrowse
2344 defaultdisplay = ibrowse
2145 __all__.append("ibrowse")
2345 __all__.append("ibrowse")
2146 else:
2346 else:
2347 # No curses (probably Windows) => use ``idump`` as the default display.
2147 defaultdisplay = idump
2348 defaultdisplay = idump
2148
2349
2149
2350
2150 # If we're running under IPython, install an IPython displayhook that
2351 # If we're running under IPython, install an IPython displayhook that
2151 # returns the object from Display.display(), else install a displayhook
2352 # returns the object from Display.display(), else install a displayhook
2152 # directly as sys.displayhook
2353 # directly as sys.displayhook
2153 try:
2354 try:
2154 from IPython import ipapi
2355 from IPython import ipapi
2155 api = ipapi.get()
2356 api = ipapi.get()
2156 except (ImportError, AttributeError):
2357 except (ImportError, AttributeError):
2157 api = None
2358 api = None
2158
2359
2159 if api is not None:
2360 if api is not None:
2160 def displayhook(self, obj):
2361 def displayhook(self, obj):
2161 if isinstance(obj, type) and issubclass(obj, Table):
2362 if isinstance(obj, type) and issubclass(obj, Table):
2162 obj = obj()
2363 obj = obj()
2163 if isinstance(obj, Table):
2364 if isinstance(obj, Table):
2164 obj = obj | defaultdisplay
2365 obj = obj | defaultdisplay
2165 if isinstance(obj, Display):
2366 if isinstance(obj, Display):
2166 return obj.display()
2367 return obj.display()
2167 else:
2368 else:
2168 raise ipapi.TryNext
2369 raise ipapi.TryNext
2169 api.set_hook("result_display", displayhook)
2370 api.set_hook("result_display", displayhook)
2170 else:
2371 else:
2171 def installdisplayhook():
2372 def installdisplayhook():
2172 _originalhook = sys.displayhook
2373 _originalhook = sys.displayhook
2173 def displayhook(obj):
2374 def displayhook(obj):
2174 if isinstance(obj, type) and issubclass(obj, Table):
2375 if isinstance(obj, type) and issubclass(obj, Table):
2175 obj = obj()
2376 obj = obj()
2176 if isinstance(obj, Table):
2377 if isinstance(obj, Table):
2177 obj = obj | defaultdisplay
2378 obj = obj | defaultdisplay
2178 if isinstance(obj, Display):
2379 if isinstance(obj, Display):
2179 return obj.display()
2380 return obj.display()
2180 else:
2381 else:
2181 _originalhook(obj)
2382 _originalhook(obj)
2182 sys.displayhook = displayhook
2383 sys.displayhook = displayhook
2183 installdisplayhook()
2384 installdisplayhook()
General Comments 0
You need to be logged in to leave comments. Login now