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