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