##// END OF EJS Templates
IPython/Extensions/astyle.py: Do a relative import of ipipe, so that...
walter.doerwald -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,401 +1,401 b''
1 """
1 """
2 ``astyle`` provides classes for adding style (foreground and background color;
2 ``astyle`` provides classes for adding style (foreground and background color;
3 bold; blink; etc.) to terminal and curses output.
3 bold; blink; etc.) to terminal and curses output.
4 """
4 """
5
5
6
6
7 import sys, os
7 import sys, os
8
8
9 try:
9 try:
10 import curses
10 import curses
11 except ImportError:
11 except ImportError:
12 curses = None
12 curses = None
13
13
14
14
15 COLOR_BLACK = 0
15 COLOR_BLACK = 0
16 COLOR_RED = 1
16 COLOR_RED = 1
17 COLOR_GREEN = 2
17 COLOR_GREEN = 2
18 COLOR_YELLOW = 3
18 COLOR_YELLOW = 3
19 COLOR_BLUE = 4
19 COLOR_BLUE = 4
20 COLOR_MAGENTA = 5
20 COLOR_MAGENTA = 5
21 COLOR_CYAN = 6
21 COLOR_CYAN = 6
22 COLOR_WHITE = 7
22 COLOR_WHITE = 7
23
23
24 A_BLINK = 1<<0 # Blinking text
24 A_BLINK = 1<<0 # Blinking text
25 A_BOLD = 1<<1 # Extra bright or bold text
25 A_BOLD = 1<<1 # Extra bright or bold text
26 A_DIM = 1<<2 # Half bright text
26 A_DIM = 1<<2 # Half bright text
27 A_REVERSE = 1<<3 # Reverse-video text
27 A_REVERSE = 1<<3 # Reverse-video text
28 A_STANDOUT = 1<<4 # The best highlighting mode available
28 A_STANDOUT = 1<<4 # The best highlighting mode available
29 A_UNDERLINE = 1<<5 # Underlined text
29 A_UNDERLINE = 1<<5 # Underlined text
30
30
31
31
32 class Style(object):
32 class Style(object):
33 """
33 """
34 Store foreground color, background color and attribute (bold, underlined
34 Store foreground color, background color and attribute (bold, underlined
35 etc.).
35 etc.).
36 """
36 """
37 __slots__ = ("fg", "bg", "attrs")
37 __slots__ = ("fg", "bg", "attrs")
38
38
39 COLORNAMES = {
39 COLORNAMES = {
40 "black": COLOR_BLACK,
40 "black": COLOR_BLACK,
41 "red": COLOR_RED,
41 "red": COLOR_RED,
42 "green": COLOR_GREEN,
42 "green": COLOR_GREEN,
43 "yellow": COLOR_YELLOW,
43 "yellow": COLOR_YELLOW,
44 "blue": COLOR_BLUE,
44 "blue": COLOR_BLUE,
45 "magenta": COLOR_MAGENTA,
45 "magenta": COLOR_MAGENTA,
46 "cyan": COLOR_CYAN,
46 "cyan": COLOR_CYAN,
47 "white": COLOR_WHITE,
47 "white": COLOR_WHITE,
48 }
48 }
49 ATTRNAMES = {
49 ATTRNAMES = {
50 "blink": A_BLINK,
50 "blink": A_BLINK,
51 "bold": A_BOLD,
51 "bold": A_BOLD,
52 "dim": A_DIM,
52 "dim": A_DIM,
53 "reverse": A_REVERSE,
53 "reverse": A_REVERSE,
54 "standout": A_STANDOUT,
54 "standout": A_STANDOUT,
55 "underline": A_UNDERLINE,
55 "underline": A_UNDERLINE,
56 }
56 }
57
57
58 def __init__(self, fg, bg, attrs=0):
58 def __init__(self, fg, bg, attrs=0):
59 """
59 """
60 Create a ``Style`` object with ``fg`` as the foreground color,
60 Create a ``Style`` object with ``fg`` as the foreground color,
61 ``bg`` as the background color and ``attrs`` as the attributes.
61 ``bg`` as the background color and ``attrs`` as the attributes.
62
62
63 Examples:
63 Examples:
64
64
65 >>> Style(COLOR_RED, COLOR_BLACK)
65 >>> Style(COLOR_RED, COLOR_BLACK)
66 >>> Style(COLOR_YELLOW, COLOR_BLUE, A_BOLD|A_UNDERLINE)
66 >>> Style(COLOR_YELLOW, COLOR_BLUE, A_BOLD|A_UNDERLINE)
67 """
67 """
68 self.fg = fg
68 self.fg = fg
69 self.bg = bg
69 self.bg = bg
70 self.attrs = attrs
70 self.attrs = attrs
71
71
72 def __call__(self, *args):
72 def __call__(self, *args):
73 text = Text()
73 text = Text()
74 for arg in args:
74 for arg in args:
75 if isinstance(arg, Text):
75 if isinstance(arg, Text):
76 text.extend(arg)
76 text.extend(arg)
77 else:
77 else:
78 text.append((self, arg))
78 text.append((self, arg))
79 return text
79 return text
80
80
81 def __eq__(self, other):
81 def __eq__(self, other):
82 return self.fg == other.fg and self.bg == other.bg and self.attrs == other.attrs
82 return self.fg == other.fg and self.bg == other.bg and self.attrs == other.attrs
83
83
84 def __neq__(self, other):
84 def __neq__(self, other):
85 return self.fg != other.fg or self.bg != other.bg or self.attrs != other.attrs
85 return self.fg != other.fg or self.bg != other.bg or self.attrs != other.attrs
86
86
87 def __repr__(self):
87 def __repr__(self):
88 color2name = ("black", "red", "green", "yellow", "blue", "magenta", "cyan", "white")
88 color2name = ("black", "red", "green", "yellow", "blue", "magenta", "cyan", "white")
89 attrs2name = ("blink", "bold", "dim", "reverse", "standout", "underline")
89 attrs2name = ("blink", "bold", "dim", "reverse", "standout", "underline")
90
90
91 return "<%s fg=%s bg=%s attrs=%s>" % (
91 return "<%s fg=%s bg=%s attrs=%s>" % (
92 self.__class__.__name__, color2name[self.fg], color2name[self.bg],
92 self.__class__.__name__, color2name[self.fg], color2name[self.bg],
93 "|".join([attrs2name[b] for b in xrange(6) if self.attrs&(1<<b)]) or 0)
93 "|".join([attrs2name[b] for b in xrange(6) if self.attrs&(1<<b)]) or 0)
94
94
95 def fromstr(cls, value):
95 def fromstr(cls, value):
96 """
96 """
97 Create a ``Style`` object from a string. The format looks like this:
97 Create a ``Style`` object from a string. The format looks like this:
98 ``"red:black:bold|blink"``.
98 ``"red:black:bold|blink"``.
99 """
99 """
100 # defaults
100 # defaults
101 fg = COLOR_WHITE
101 fg = COLOR_WHITE
102 bg = COLOR_BLACK
102 bg = COLOR_BLACK
103 attrs = 0
103 attrs = 0
104
104
105 parts = value.split(":")
105 parts = value.split(":")
106 if len(parts) > 0:
106 if len(parts) > 0:
107 fg = cls.COLORNAMES[parts[0].lower()]
107 fg = cls.COLORNAMES[parts[0].lower()]
108 if len(parts) > 1:
108 if len(parts) > 1:
109 bg = cls.COLORNAMES[parts[1].lower()]
109 bg = cls.COLORNAMES[parts[1].lower()]
110 if len(parts) > 2:
110 if len(parts) > 2:
111 for strattr in parts[2].split("|"):
111 for strattr in parts[2].split("|"):
112 attrs |= cls.ATTRNAMES[strattr.lower()]
112 attrs |= cls.ATTRNAMES[strattr.lower()]
113 return cls(fg, bg, attrs)
113 return cls(fg, bg, attrs)
114 fromstr = classmethod(fromstr)
114 fromstr = classmethod(fromstr)
115
115
116 def fromenv(cls, name, default):
116 def fromenv(cls, name, default):
117 """
117 """
118 Create a ``Style`` from an environment variable named ``name``
118 Create a ``Style`` from an environment variable named ``name``
119 (using ``default`` if the environment variable doesn't exist).
119 (using ``default`` if the environment variable doesn't exist).
120 """
120 """
121 return cls.fromstr(os.environ.get(name, default))
121 return cls.fromstr(os.environ.get(name, default))
122 fromenv = classmethod(fromenv)
122 fromenv = classmethod(fromenv)
123
123
124
124
125 def switchstyle(s1, s2):
125 def switchstyle(s1, s2):
126 """
126 """
127 Return the ANSI escape sequence needed to switch from style ``s1`` to
127 Return the ANSI escape sequence needed to switch from style ``s1`` to
128 style ``s2``.
128 style ``s2``.
129 """
129 """
130 attrmask = (A_BLINK|A_BOLD|A_UNDERLINE|A_REVERSE)
130 attrmask = (A_BLINK|A_BOLD|A_UNDERLINE|A_REVERSE)
131 a1 = s1.attrs & attrmask
131 a1 = s1.attrs & attrmask
132 a2 = s2.attrs & attrmask
132 a2 = s2.attrs & attrmask
133
133
134 args = []
134 args = []
135 if s1 != s2:
135 if s1 != s2:
136 # do we have to get rid of the bold/underline/blink bit?
136 # do we have to get rid of the bold/underline/blink bit?
137 # (can only be done by a reset)
137 # (can only be done by a reset)
138 # use reset when our target color is the default color
138 # use reset when our target color is the default color
139 # (this is shorter than 37;40)
139 # (this is shorter than 37;40)
140 if (a1 & ~a2 or s2==style_default):
140 if (a1 & ~a2 or s2==style_default):
141 args.append("0")
141 args.append("0")
142 s1 = style_default
142 s1 = style_default
143 a1 = 0
143 a1 = 0
144
144
145 # now we know that old and new color have the same boldness,
145 # now we know that old and new color have the same boldness,
146 # or the new color is bold and the old isn't,
146 # or the new color is bold and the old isn't,
147 # i.e. we only might have to switch bold on, not off
147 # i.e. we only might have to switch bold on, not off
148 if not (a1 & A_BOLD) and (a2 & A_BOLD):
148 if not (a1 & A_BOLD) and (a2 & A_BOLD):
149 args.append("1")
149 args.append("1")
150
150
151 # Fix underline
151 # Fix underline
152 if not (a1 & A_UNDERLINE) and (a2 & A_UNDERLINE):
152 if not (a1 & A_UNDERLINE) and (a2 & A_UNDERLINE):
153 args.append("4")
153 args.append("4")
154
154
155 # Fix blink
155 # Fix blink
156 if not (a1 & A_BLINK) and (a2 & A_BLINK):
156 if not (a1 & A_BLINK) and (a2 & A_BLINK):
157 args.append("5")
157 args.append("5")
158
158
159 # Fix reverse
159 # Fix reverse
160 if not (a1 & A_REVERSE) and (a2 & A_REVERSE):
160 if not (a1 & A_REVERSE) and (a2 & A_REVERSE):
161 args.append("7")
161 args.append("7")
162
162
163 # Fix foreground color
163 # Fix foreground color
164 if s1.fg != s2.fg:
164 if s1.fg != s2.fg:
165 args.append("3%d" % s2.fg)
165 args.append("3%d" % s2.fg)
166
166
167 # Finally fix the background color
167 # Finally fix the background color
168 if s1.bg != s2.bg:
168 if s1.bg != s2.bg:
169 args.append("4%d" % s2.bg)
169 args.append("4%d" % s2.bg)
170
170
171 if args:
171 if args:
172 return "\033[%sm" % ";".join(args)
172 return "\033[%sm" % ";".join(args)
173 return ""
173 return ""
174
174
175
175
176 class Text(list):
176 class Text(list):
177 """
177 """
178 A colored string. A ``Text`` object is a sequence, the sequence
178 A colored string. A ``Text`` object is a sequence, the sequence
179 items will be ``(style, string)`` tuples.
179 items will be ``(style, string)`` tuples.
180 """
180 """
181
181
182 def __init__(self, *args):
182 def __init__(self, *args):
183 list.__init__(self)
183 list.__init__(self)
184 self.append(*args)
184 self.append(*args)
185
185
186 def __repr__(self):
186 def __repr__(self):
187 return "%s.%s(%s)" % (
187 return "%s.%s(%s)" % (
188 self.__class__.__module__, self.__class__.__name__,
188 self.__class__.__module__, self.__class__.__name__,
189 list.__repr__(self)[1:-1])
189 list.__repr__(self)[1:-1])
190
190
191 def append(self, *args):
191 def append(self, *args):
192 for arg in args:
192 for arg in args:
193 if isinstance(arg, Text):
193 if isinstance(arg, Text):
194 self.extend(arg)
194 self.extend(arg)
195 elif isinstance(arg, tuple): # must be (style, string)
195 elif isinstance(arg, tuple): # must be (style, string)
196 list.append(self, arg)
196 list.append(self, arg)
197 elif isinstance(arg, unicode):
197 elif isinstance(arg, unicode):
198 list.append(self, (style_default, arg))
198 list.append(self, (style_default, arg))
199 else:
199 else:
200 list.append(self, (style_default, str(arg)))
200 list.append(self, (style_default, str(arg)))
201
201
202 def insert(self, index, *args):
202 def insert(self, index, *args):
203 self[index:index] = Text(*args)
203 self[index:index] = Text(*args)
204
204
205 def __add__(self, other):
205 def __add__(self, other):
206 new = Text()
206 new = Text()
207 new.append(self)
207 new.append(self)
208 new.append(other)
208 new.append(other)
209 return new
209 return new
210
210
211 def __iadd__(self, other):
211 def __iadd__(self, other):
212 self.append(other)
212 self.append(other)
213 return self
213 return self
214
214
215 def format(self, styled=True):
215 def format(self, styled=True):
216 """
216 """
217 This generator yields the strings that will make up the final
217 This generator yields the strings that will make up the final
218 colorized string.
218 colorized string.
219 """
219 """
220 if styled:
220 if styled:
221 oldstyle = style_default
221 oldstyle = style_default
222 for (style, string) in self:
222 for (style, string) in self:
223 if not isinstance(style, (int, long)):
223 if not isinstance(style, (int, long)):
224 switch = switchstyle(oldstyle, style)
224 switch = switchstyle(oldstyle, style)
225 if switch:
225 if switch:
226 yield switch
226 yield switch
227 if string:
227 if string:
228 yield string
228 yield string
229 oldstyle = style
229 oldstyle = style
230 switch = switchstyle(oldstyle, style_default)
230 switch = switchstyle(oldstyle, style_default)
231 if switch:
231 if switch:
232 yield switch
232 yield switch
233 else:
233 else:
234 for (style, string) in self:
234 for (style, string) in self:
235 if not isinstance(style, (int, long)):
235 if not isinstance(style, (int, long)):
236 yield string
236 yield string
237
237
238 def string(self, styled=True):
238 def string(self, styled=True):
239 """
239 """
240 Return the resulting string (with escape sequences, if ``styled``
240 Return the resulting string (with escape sequences, if ``styled``
241 is true).
241 is true).
242 """
242 """
243 return "".join(self.format(styled))
243 return "".join(self.format(styled))
244
244
245 def __str__(self):
245 def __str__(self):
246 """
246 """
247 Return ``self`` as a string (without ANSI escape sequences).
247 Return ``self`` as a string (without ANSI escape sequences).
248 """
248 """
249 return self.string(False)
249 return self.string(False)
250
250
251 def write(self, stream, styled=True):
251 def write(self, stream, styled=True):
252 """
252 """
253 Write ``self`` to the output stream ``stream`` (with escape sequences,
253 Write ``self`` to the output stream ``stream`` (with escape sequences,
254 if ``styled`` is true).
254 if ``styled`` is true).
255 """
255 """
256 for part in self.format(styled):
256 for part in self.format(styled):
257 stream.write(part)
257 stream.write(part)
258
258
259
259
260 try:
260 try:
261 from IPython.Extensions import ipipe
261 import ipipe
262 except ImportError:
262 except ImportError:
263 pass
263 pass
264 else:
264 else:
265 def xrepr_astyle_text(self, mode="default"):
265 def xrepr_astyle_text(self, mode="default"):
266 yield (-1, True)
266 yield (-1, True)
267 for info in self:
267 for info in self:
268 yield info
268 yield info
269 try:
269 try:
270 ipipe.xrepr.when_type(Text)(xrepr_astyle_text)
270 ipipe.xrepr.when_type(Text)(xrepr_astyle_text)
271 except Exception:
271 except Exception:
272 pass
272 pass
273
273
274
274
275 def streamstyle(stream, styled=None):
275 def streamstyle(stream, styled=None):
276 """
276 """
277 If ``styled`` is ``None``, return whether ``stream`` refers to a terminal.
277 If ``styled`` is ``None``, return whether ``stream`` refers to a terminal.
278 If this can't be determined (either because ``stream`` doesn't refer to a
278 If this can't be determined (either because ``stream`` doesn't refer to a
279 real OS file, or because you're on Windows) return ``False``. If ``styled``
279 real OS file, or because you're on Windows) return ``False``. If ``styled``
280 is not ``None`` ``styled`` will be returned unchanged.
280 is not ``None`` ``styled`` will be returned unchanged.
281 """
281 """
282 if styled is None:
282 if styled is None:
283 try:
283 try:
284 styled = os.isatty(stream.fileno())
284 styled = os.isatty(stream.fileno())
285 except (KeyboardInterrupt, SystemExit):
285 except (KeyboardInterrupt, SystemExit):
286 raise
286 raise
287 except Exception:
287 except Exception:
288 styled = False
288 styled = False
289 return styled
289 return styled
290
290
291
291
292 def write(stream, styled, *texts):
292 def write(stream, styled, *texts):
293 """
293 """
294 Write ``texts`` to ``stream``.
294 Write ``texts`` to ``stream``.
295 """
295 """
296 text = Text(*texts)
296 text = Text(*texts)
297 text.write(stream, streamstyle(stream, styled))
297 text.write(stream, streamstyle(stream, styled))
298
298
299
299
300 def writeln(stream, styled, *texts):
300 def writeln(stream, styled, *texts):
301 """
301 """
302 Write ``texts`` to ``stream`` and finish with a line feed.
302 Write ``texts`` to ``stream`` and finish with a line feed.
303 """
303 """
304 write(stream, styled, *texts)
304 write(stream, styled, *texts)
305 stream.write("\n")
305 stream.write("\n")
306
306
307
307
308 class Stream(object):
308 class Stream(object):
309 """
309 """
310 Stream wrapper that adds color output.
310 Stream wrapper that adds color output.
311 """
311 """
312 def __init__(self, stream, styled=None):
312 def __init__(self, stream, styled=None):
313 self.stream = stream
313 self.stream = stream
314 self.styled = streamstyle(stream, styled)
314 self.styled = streamstyle(stream, styled)
315
315
316 def write(self, *texts):
316 def write(self, *texts):
317 write(self.stream, self.styled, *texts)
317 write(self.stream, self.styled, *texts)
318
318
319 def writeln(self, *texts):
319 def writeln(self, *texts):
320 writeln(self.stream, self.styled, *texts)
320 writeln(self.stream, self.styled, *texts)
321
321
322 def __getattr__(self, name):
322 def __getattr__(self, name):
323 return getattr(self.stream, name)
323 return getattr(self.stream, name)
324
324
325
325
326 class stdout(object):
326 class stdout(object):
327 """
327 """
328 Stream wrapper for ``sys.stdout`` that adds color output.
328 Stream wrapper for ``sys.stdout`` that adds color output.
329 """
329 """
330 def write(self, *texts):
330 def write(self, *texts):
331 write(sys.stdout, None, *texts)
331 write(sys.stdout, None, *texts)
332
332
333 def writeln(self, *texts):
333 def writeln(self, *texts):
334 writeln(sys.stdout, None, *texts)
334 writeln(sys.stdout, None, *texts)
335
335
336 def __getattr__(self, name):
336 def __getattr__(self, name):
337 return getattr(sys.stdout, name)
337 return getattr(sys.stdout, name)
338 stdout = stdout()
338 stdout = stdout()
339
339
340
340
341 class stderr(object):
341 class stderr(object):
342 """
342 """
343 Stream wrapper for ``sys.stderr`` that adds color output.
343 Stream wrapper for ``sys.stderr`` that adds color output.
344 """
344 """
345 def write(self, *texts):
345 def write(self, *texts):
346 write(sys.stderr, None, *texts)
346 write(sys.stderr, None, *texts)
347
347
348 def writeln(self, *texts):
348 def writeln(self, *texts):
349 writeln(sys.stderr, None, *texts)
349 writeln(sys.stderr, None, *texts)
350
350
351 def __getattr__(self, name):
351 def __getattr__(self, name):
352 return getattr(sys.stdout, name)
352 return getattr(sys.stdout, name)
353 stderr = stderr()
353 stderr = stderr()
354
354
355
355
356 if curses is not None:
356 if curses is not None:
357 # This is probably just range(8)
357 # This is probably just range(8)
358 COLOR2CURSES = [
358 COLOR2CURSES = [
359 COLOR_BLACK,
359 COLOR_BLACK,
360 COLOR_RED,
360 COLOR_RED,
361 COLOR_GREEN,
361 COLOR_GREEN,
362 COLOR_YELLOW,
362 COLOR_YELLOW,
363 COLOR_BLUE,
363 COLOR_BLUE,
364 COLOR_MAGENTA,
364 COLOR_MAGENTA,
365 COLOR_CYAN,
365 COLOR_CYAN,
366 COLOR_WHITE,
366 COLOR_WHITE,
367 ]
367 ]
368
368
369 A2CURSES = {
369 A2CURSES = {
370 A_BLINK: curses.A_BLINK,
370 A_BLINK: curses.A_BLINK,
371 A_BOLD: curses.A_BOLD,
371 A_BOLD: curses.A_BOLD,
372 A_DIM: curses.A_DIM,
372 A_DIM: curses.A_DIM,
373 A_REVERSE: curses.A_REVERSE,
373 A_REVERSE: curses.A_REVERSE,
374 A_STANDOUT: curses.A_STANDOUT,
374 A_STANDOUT: curses.A_STANDOUT,
375 A_UNDERLINE: curses.A_UNDERLINE,
375 A_UNDERLINE: curses.A_UNDERLINE,
376 }
376 }
377
377
378
378
379 # default style
379 # default style
380 style_default = Style.fromstr("white:black")
380 style_default = Style.fromstr("white:black")
381
381
382 # Styles for datatypes
382 # Styles for datatypes
383 style_type_none = Style.fromstr("magenta:black")
383 style_type_none = Style.fromstr("magenta:black")
384 style_type_bool = Style.fromstr("magenta:black")
384 style_type_bool = Style.fromstr("magenta:black")
385 style_type_number = Style.fromstr("yellow:black")
385 style_type_number = Style.fromstr("yellow:black")
386 style_type_datetime = Style.fromstr("magenta:black")
386 style_type_datetime = Style.fromstr("magenta:black")
387 style_type_type = Style.fromstr("cyan:black")
387 style_type_type = Style.fromstr("cyan:black")
388
388
389 # Style for URLs and file/directory names
389 # Style for URLs and file/directory names
390 style_url = Style.fromstr("green:black")
390 style_url = Style.fromstr("green:black")
391 style_dir = Style.fromstr("cyan:black")
391 style_dir = Style.fromstr("cyan:black")
392 style_file = Style.fromstr("green:black")
392 style_file = Style.fromstr("green:black")
393
393
394 # Style for ellipsis (when an output has been shortened
394 # Style for ellipsis (when an output has been shortened
395 style_ellisis = Style.fromstr("red:black")
395 style_ellisis = Style.fromstr("red:black")
396
396
397 # Style for displaying exceptions
397 # Style for displaying exceptions
398 style_error = Style.fromstr("red:black")
398 style_error = Style.fromstr("red:black")
399
399
400 # Style for displaying non-existing attributes
400 # Style for displaying non-existing attributes
401 style_nodata = Style.fromstr("red:black")
401 style_nodata = Style.fromstr("red:black")
@@ -1,1667 +1,1667 b''
1 # -*- coding: iso-8859-1 -*-
1 # -*- coding: iso-8859-1 -*-
2
2
3 import curses, fcntl, signal, struct, tty, textwrap, inspect
3 import curses, fcntl, signal, struct, tty, textwrap, inspect
4
4
5 import astyle, ipipe
5 import astyle, ipipe
6
6
7
7
8 # Python 2.3 compatibility
8 # Python 2.3 compatibility
9 try:
9 try:
10 set
10 set
11 except NameError:
11 except NameError:
12 import sets
12 import sets
13 set = sets.Set
13 set = sets.Set
14
14
15 # Python 2.3 compatibility
15 # Python 2.3 compatibility
16 try:
16 try:
17 sorted
17 sorted
18 except NameError:
18 except NameError:
19 from ipipe import sorted
19 from ipipe import sorted
20
20
21
21
22 class UnassignedKeyError(Exception):
22 class UnassignedKeyError(Exception):
23 """
23 """
24 Exception that is used for reporting unassigned keys.
24 Exception that is used for reporting unassigned keys.
25 """
25 """
26
26
27
27
28 class UnknownCommandError(Exception):
28 class UnknownCommandError(Exception):
29 """
29 """
30 Exception that is used for reporting unknown commands (this should never
30 Exception that is used for reporting unknown commands (this should never
31 happen).
31 happen).
32 """
32 """
33
33
34
34
35 class CommandError(Exception):
35 class CommandError(Exception):
36 """
36 """
37 Exception that is used for reporting that a command can't be executed.
37 Exception that is used for reporting that a command can't be executed.
38 """
38 """
39
39
40
40
41 class Keymap(dict):
41 class Keymap(dict):
42 """
42 """
43 Stores mapping of keys to commands.
43 Stores mapping of keys to commands.
44 """
44 """
45 def __init__(self):
45 def __init__(self):
46 self._keymap = {}
46 self._keymap = {}
47
47
48 def __setitem__(self, key, command):
48 def __setitem__(self, key, command):
49 if isinstance(key, str):
49 if isinstance(key, str):
50 for c in key:
50 for c in key:
51 dict.__setitem__(self, ord(c), command)
51 dict.__setitem__(self, ord(c), command)
52 else:
52 else:
53 dict.__setitem__(self, key, command)
53 dict.__setitem__(self, key, command)
54
54
55 def __getitem__(self, key):
55 def __getitem__(self, key):
56 if isinstance(key, str):
56 if isinstance(key, str):
57 key = ord(key)
57 key = ord(key)
58 return dict.__getitem__(self, key)
58 return dict.__getitem__(self, key)
59
59
60 def __detitem__(self, key):
60 def __detitem__(self, key):
61 if isinstance(key, str):
61 if isinstance(key, str):
62 key = ord(key)
62 key = ord(key)
63 dict.__detitem__(self, key)
63 dict.__detitem__(self, key)
64
64
65 def register(self, command, *keys):
65 def register(self, command, *keys):
66 for key in keys:
66 for key in keys:
67 self[key] = command
67 self[key] = command
68
68
69 def get(self, key, default=None):
69 def get(self, key, default=None):
70 if isinstance(key, str):
70 if isinstance(key, str):
71 key = ord(key)
71 key = ord(key)
72 return dict.get(self, key, default)
72 return dict.get(self, key, default)
73
73
74 def findkey(self, command, default=ipipe.noitem):
74 def findkey(self, command, default=ipipe.noitem):
75 for (key, commandcandidate) in self.iteritems():
75 for (key, commandcandidate) in self.iteritems():
76 if commandcandidate == command:
76 if commandcandidate == command:
77 return key
77 return key
78 if default is ipipe.noitem:
78 if default is ipipe.noitem:
79 raise KeyError(command)
79 raise KeyError(command)
80 return default
80 return default
81
81
82
82
83 class _BrowserCachedItem(object):
83 class _BrowserCachedItem(object):
84 # This is used internally by ``ibrowse`` to store a item together with its
84 # This is used internally by ``ibrowse`` to store a item together with its
85 # marked status.
85 # marked status.
86 __slots__ = ("item", "marked")
86 __slots__ = ("item", "marked")
87
87
88 def __init__(self, item):
88 def __init__(self, item):
89 self.item = item
89 self.item = item
90 self.marked = False
90 self.marked = False
91
91
92
92
93 class _BrowserHelp(object):
93 class _BrowserHelp(object):
94 style_header = astyle.Style.fromstr("yellow:black:bold")
94 style_header = astyle.Style.fromstr("yellow:black:bold")
95 # This is used internally by ``ibrowse`` for displaying the help screen.
95 # This is used internally by ``ibrowse`` for displaying the help screen.
96 def __init__(self, browser):
96 def __init__(self, browser):
97 self.browser = browser
97 self.browser = browser
98
98
99 def __xrepr__(self, mode):
99 def __xrepr__(self, mode):
100 yield (-1, True)
100 yield (-1, True)
101 if mode == "header" or mode == "footer":
101 if mode == "header" or mode == "footer":
102 yield (astyle.style_default, "ibrowse help screen")
102 yield (astyle.style_default, "ibrowse help screen")
103 else:
103 else:
104 yield (astyle.style_default, repr(self))
104 yield (astyle.style_default, repr(self))
105
105
106 def __iter__(self):
106 def __iter__(self):
107 # Get reverse key mapping
107 # Get reverse key mapping
108 allkeys = {}
108 allkeys = {}
109 for (key, cmd) in self.browser.keymap.iteritems():
109 for (key, cmd) in self.browser.keymap.iteritems():
110 allkeys.setdefault(cmd, []).append(key)
110 allkeys.setdefault(cmd, []).append(key)
111
111
112 fields = ("key", "description")
112 fields = ("key", "description")
113
113
114 commands = []
114 commands = []
115 for name in dir(self.browser):
115 for name in dir(self.browser):
116 if name.startswith("cmd_"):
116 if name.startswith("cmd_"):
117 command = getattr(self.browser, name)
117 command = getattr(self.browser, name)
118 commands.append((inspect.getsourcelines(command)[-1], name[4:], command))
118 commands.append((inspect.getsourcelines(command)[-1], name[4:], command))
119 commands.sort()
119 commands.sort()
120 commands = [(c[1], c[2]) for c in commands]
120 commands = [(c[1], c[2]) for c in commands]
121 for (i, (name, command)) in enumerate(commands):
121 for (i, (name, command)) in enumerate(commands):
122 if i:
122 if i:
123 yield ipipe.Fields(fields, key="", description="")
123 yield ipipe.Fields(fields, key="", description="")
124
124
125 description = command.__doc__
125 description = command.__doc__
126 if description is None:
126 if description is None:
127 lines = []
127 lines = []
128 else:
128 else:
129 lines = [l.strip() for l in description.splitlines() if l.strip()]
129 lines = [l.strip() for l in description.splitlines() if l.strip()]
130 description = "\n".join(lines)
130 description = "\n".join(lines)
131 lines = textwrap.wrap(description, 60)
131 lines = textwrap.wrap(description, 60)
132 keys = allkeys.get(name, [])
132 keys = allkeys.get(name, [])
133
133
134 yield ipipe.Fields(fields, key="", description=astyle.Text((self.style_header, name)))
134 yield ipipe.Fields(fields, key="", description=astyle.Text((self.style_header, name)))
135 for i in xrange(max(len(keys), len(lines))):
135 for i in xrange(max(len(keys), len(lines))):
136 try:
136 try:
137 key = self.browser.keylabel(keys[i])
137 key = self.browser.keylabel(keys[i])
138 except IndexError:
138 except IndexError:
139 key = ""
139 key = ""
140 try:
140 try:
141 line = lines[i]
141 line = lines[i]
142 except IndexError:
142 except IndexError:
143 line = ""
143 line = ""
144 yield ipipe.Fields(fields, key=key, description=line)
144 yield ipipe.Fields(fields, key=key, description=line)
145
145
146
146
147 class _BrowserLevel(object):
147 class _BrowserLevel(object):
148 # This is used internally to store the state (iterator, fetch items,
148 # This is used internally to store the state (iterator, fetch items,
149 # position of cursor and screen, etc.) of one browser level
149 # position of cursor and screen, etc.) of one browser level
150 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
150 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
151 # a stack.
151 # a stack.
152 def __init__(self, browser, input, iterator, mainsizey, *attrs):
152 def __init__(self, browser, input, iterator, mainsizey, *attrs):
153 self.browser = browser
153 self.browser = browser
154 self.input = input
154 self.input = input
155 self.header = [x for x in ipipe.xrepr(input, "header") if not isinstance(x[0], int)]
155 self.header = [x for x in ipipe.xrepr(input, "header") if not isinstance(x[0], int)]
156 # iterator for the input
156 # iterator for the input
157 self.iterator = iterator
157 self.iterator = iterator
158
158
159 # is the iterator exhausted?
159 # is the iterator exhausted?
160 self.exhausted = False
160 self.exhausted = False
161
161
162 # attributes to be display (autodetected if empty)
162 # attributes to be display (autodetected if empty)
163 self.attrs = attrs
163 self.attrs = attrs
164
164
165 # fetched items (+ marked flag)
165 # fetched items (+ marked flag)
166 self.items = ipipe.deque()
166 self.items = ipipe.deque()
167
167
168 # Number of marked objects
168 # Number of marked objects
169 self.marked = 0
169 self.marked = 0
170
170
171 # Vertical cursor position
171 # Vertical cursor position
172 self.cury = 0
172 self.cury = 0
173
173
174 # Horizontal cursor position
174 # Horizontal cursor position
175 self.curx = 0
175 self.curx = 0
176
176
177 # Index of first data column
177 # Index of first data column
178 self.datastartx = 0
178 self.datastartx = 0
179
179
180 # Index of first data line
180 # Index of first data line
181 self.datastarty = 0
181 self.datastarty = 0
182
182
183 # height of the data display area
183 # height of the data display area
184 self.mainsizey = mainsizey
184 self.mainsizey = mainsizey
185
185
186 # width of the data display area (changes when scrolling)
186 # width of the data display area (changes when scrolling)
187 self.mainsizex = 0
187 self.mainsizex = 0
188
188
189 # Size of row number (changes when scrolling)
189 # Size of row number (changes when scrolling)
190 self.numbersizex = 0
190 self.numbersizex = 0
191
191
192 # Attributes to display (in this order)
192 # Attributes to display (in this order)
193 self.displayattrs = []
193 self.displayattrs = []
194
194
195 # index and attribute under the cursor
195 # index and attribute under the cursor
196 self.displayattr = (None, ipipe.noitem)
196 self.displayattr = (None, ipipe.noitem)
197
197
198 # Maps attributes to column widths
198 # Maps attributes to column widths
199 self.colwidths = {}
199 self.colwidths = {}
200
200
201 # Set of hidden attributes
201 # Set of hidden attributes
202 self.hiddenattrs = set()
202 self.hiddenattrs = set()
203
203
204 # This takes care of all the caches etc.
204 # This takes care of all the caches etc.
205 self.moveto(0, 0, refresh=True)
205 self.moveto(0, 0, refresh=True)
206
206
207 def fetch(self, count):
207 def fetch(self, count):
208 # Try to fill ``self.items`` with at least ``count`` objects.
208 # Try to fill ``self.items`` with at least ``count`` objects.
209 have = len(self.items)
209 have = len(self.items)
210 while not self.exhausted and have < count:
210 while not self.exhausted and have < count:
211 try:
211 try:
212 item = self.iterator.next()
212 item = self.iterator.next()
213 except StopIteration:
213 except StopIteration:
214 self.exhausted = True
214 self.exhausted = True
215 break
215 break
216 except (KeyboardInterrupt, SystemExit):
216 except (KeyboardInterrupt, SystemExit):
217 raise
217 raise
218 except Exception, exc:
218 except Exception, exc:
219 have += 1
219 have += 1
220 self.items.append(_BrowserCachedItem(exc))
220 self.items.append(_BrowserCachedItem(exc))
221 self.exhausted = True
221 self.exhausted = True
222 break
222 break
223 else:
223 else:
224 have += 1
224 have += 1
225 self.items.append(_BrowserCachedItem(item))
225 self.items.append(_BrowserCachedItem(item))
226
226
227 def calcdisplayattrs(self):
227 def calcdisplayattrs(self):
228 # Calculate which attributes are available from the objects that are
228 # Calculate which attributes are available from the objects that are
229 # currently visible on screen (and store it in ``self.displayattrs``)
229 # currently visible on screen (and store it in ``self.displayattrs``)
230
230
231 attrs = set()
231 attrs = set()
232 self.displayattrs = []
232 self.displayattrs = []
233 if self.attrs:
233 if self.attrs:
234 # If the browser object specifies a fixed list of attributes,
234 # If the browser object specifies a fixed list of attributes,
235 # simply use it (removing hidden attributes).
235 # simply use it (removing hidden attributes).
236 for attr in self.attrs:
236 for attr in self.attrs:
237 attr = ipipe.upgradexattr(attr)
237 attr = ipipe.upgradexattr(attr)
238 if attr not in attrs and attr not in self.hiddenattrs:
238 if attr not in attrs and attr not in self.hiddenattrs:
239 self.displayattrs.append(attr)
239 self.displayattrs.append(attr)
240 attrs.add(attr)
240 attrs.add(attr)
241 else:
241 else:
242 endy = min(self.datastarty+self.mainsizey, len(self.items))
242 endy = min(self.datastarty+self.mainsizey, len(self.items))
243 for i in xrange(self.datastarty, endy):
243 for i in xrange(self.datastarty, endy):
244 for attr in ipipe.xattrs(self.items[i].item, "default"):
244 for attr in ipipe.xattrs(self.items[i].item, "default"):
245 if attr not in attrs and attr not in self.hiddenattrs:
245 if attr not in attrs and attr not in self.hiddenattrs:
246 self.displayattrs.append(attr)
246 self.displayattrs.append(attr)
247 attrs.add(attr)
247 attrs.add(attr)
248
248
249 def getrow(self, i):
249 def getrow(self, i):
250 # Return a dictionary with the attributes for the object
250 # Return a dictionary with the attributes for the object
251 # ``self.items[i]``. Attribute names are taken from
251 # ``self.items[i]``. Attribute names are taken from
252 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
252 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
253 # called before.
253 # called before.
254 row = {}
254 row = {}
255 item = self.items[i].item
255 item = self.items[i].item
256 for attr in self.displayattrs:
256 for attr in self.displayattrs:
257 try:
257 try:
258 value = attr.value(item)
258 value = attr.value(item)
259 except (KeyboardInterrupt, SystemExit):
259 except (KeyboardInterrupt, SystemExit):
260 raise
260 raise
261 except Exception, exc:
261 except Exception, exc:
262 value = exc
262 value = exc
263 # only store attribute if it exists (or we got an exception)
263 # only store attribute if it exists (or we got an exception)
264 if value is not ipipe.noitem:
264 if value is not ipipe.noitem:
265 # remember alignment, length and colored text
265 # remember alignment, length and colored text
266 row[attr] = ipipe.xformat(value, "cell", self.browser.maxattrlength)
266 row[attr] = ipipe.xformat(value, "cell", self.browser.maxattrlength)
267 return row
267 return row
268
268
269 def calcwidths(self):
269 def calcwidths(self):
270 # Recalculate the displayed fields and their widths.
270 # Recalculate the displayed fields and their widths.
271 # ``calcdisplayattrs()'' must have been called and the cache
271 # ``calcdisplayattrs()'' must have been called and the cache
272 # for attributes of the objects on screen (``self.displayrows``)
272 # for attributes of the objects on screen (``self.displayrows``)
273 # must have been filled. This sets ``self.colwidths`` which maps
273 # must have been filled. This sets ``self.colwidths`` which maps
274 # attribute descriptors to widths.
274 # attribute descriptors to widths.
275 self.colwidths = {}
275 self.colwidths = {}
276 for row in self.displayrows:
276 for row in self.displayrows:
277 for attr in self.displayattrs:
277 for attr in self.displayattrs:
278 try:
278 try:
279 length = row[attr][1]
279 length = row[attr][1]
280 except KeyError:
280 except KeyError:
281 length = 0
281 length = 0
282 # always add attribute to colwidths, even if it doesn't exist
282 # always add attribute to colwidths, even if it doesn't exist
283 if attr not in self.colwidths:
283 if attr not in self.colwidths:
284 self.colwidths[attr] = len(attr.name())
284 self.colwidths[attr] = len(attr.name())
285 newwidth = max(self.colwidths[attr], length)
285 newwidth = max(self.colwidths[attr], length)
286 self.colwidths[attr] = newwidth
286 self.colwidths[attr] = newwidth
287
287
288 # How many characters do we need to paint the largest item number?
288 # How many characters do we need to paint the largest item number?
289 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
289 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
290 # How must space have we got to display data?
290 # How must space have we got to display data?
291 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
291 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
292 # width of all columns
292 # width of all columns
293 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
293 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
294
294
295 def calcdisplayattr(self):
295 def calcdisplayattr(self):
296 # Find out which attribute the cursor is on and store this
296 # Find out which attribute the cursor is on and store this
297 # information in ``self.displayattr``.
297 # information in ``self.displayattr``.
298 pos = 0
298 pos = 0
299 for (i, attr) in enumerate(self.displayattrs):
299 for (i, attr) in enumerate(self.displayattrs):
300 if pos+self.colwidths[attr] >= self.curx:
300 if pos+self.colwidths[attr] >= self.curx:
301 self.displayattr = (i, attr)
301 self.displayattr = (i, attr)
302 break
302 break
303 pos += self.colwidths[attr]+1
303 pos += self.colwidths[attr]+1
304 else:
304 else:
305 self.displayattr = (None, ipipe.noitem)
305 self.displayattr = (None, ipipe.noitem)
306
306
307 def moveto(self, x, y, refresh=False):
307 def moveto(self, x, y, refresh=False):
308 # Move the cursor to the position ``(x,y)`` (in data coordinates,
308 # Move the cursor to the position ``(x,y)`` (in data coordinates,
309 # not in screen coordinates). If ``refresh`` is true, all cached
309 # not in screen coordinates). If ``refresh`` is true, all cached
310 # values will be recalculated (e.g. because the list has been
310 # values will be recalculated (e.g. because the list has been
311 # resorted, so screen positions etc. are no longer valid).
311 # resorted, so screen positions etc. are no longer valid).
312 olddatastarty = self.datastarty
312 olddatastarty = self.datastarty
313 oldx = self.curx
313 oldx = self.curx
314 oldy = self.cury
314 oldy = self.cury
315 x = int(x+0.5)
315 x = int(x+0.5)
316 y = int(y+0.5)
316 y = int(y+0.5)
317 newx = x # remember where we wanted to move
317 newx = x # remember where we wanted to move
318 newy = y # remember where we wanted to move
318 newy = y # remember where we wanted to move
319
319
320 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
320 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
321 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
321 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
322
322
323 # Make sure that the cursor didn't leave the main area vertically
323 # Make sure that the cursor didn't leave the main area vertically
324 if y < 0:
324 if y < 0:
325 y = 0
325 y = 0
326 # try to get enough items to fill the screen
326 # try to get enough items to fill the screen
327 self.fetch(max(y+scrollbordery+1, self.mainsizey))
327 self.fetch(max(y+scrollbordery+1, self.mainsizey))
328 if y >= len(self.items):
328 if y >= len(self.items):
329 y = max(0, len(self.items)-1)
329 y = max(0, len(self.items)-1)
330
330
331 # Make sure that the cursor stays on screen vertically
331 # Make sure that the cursor stays on screen vertically
332 if y < self.datastarty+scrollbordery:
332 if y < self.datastarty+scrollbordery:
333 self.datastarty = max(0, y-scrollbordery)
333 self.datastarty = max(0, y-scrollbordery)
334 elif y >= self.datastarty+self.mainsizey-scrollbordery:
334 elif y >= self.datastarty+self.mainsizey-scrollbordery:
335 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
335 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
336 len(self.items)-self.mainsizey))
336 len(self.items)-self.mainsizey))
337
337
338 if refresh: # Do we need to refresh the complete display?
338 if refresh: # Do we need to refresh the complete display?
339 self.calcdisplayattrs()
339 self.calcdisplayattrs()
340 endy = min(self.datastarty+self.mainsizey, len(self.items))
340 endy = min(self.datastarty+self.mainsizey, len(self.items))
341 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
341 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
342 self.calcwidths()
342 self.calcwidths()
343 # Did we scroll vertically => update displayrows
343 # Did we scroll vertically => update displayrows
344 # and various other attributes
344 # and various other attributes
345 elif self.datastarty != olddatastarty:
345 elif self.datastarty != olddatastarty:
346 # Recalculate which attributes we have to display
346 # Recalculate which attributes we have to display
347 olddisplayattrs = self.displayattrs
347 olddisplayattrs = self.displayattrs
348 self.calcdisplayattrs()
348 self.calcdisplayattrs()
349 # If there are new attributes, recreate the cache
349 # If there are new attributes, recreate the cache
350 if self.displayattrs != olddisplayattrs:
350 if self.displayattrs != olddisplayattrs:
351 endy = min(self.datastarty+self.mainsizey, len(self.items))
351 endy = min(self.datastarty+self.mainsizey, len(self.items))
352 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
352 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
353 elif self.datastarty<olddatastarty: # we did scroll up
353 elif self.datastarty<olddatastarty: # we did scroll up
354 # drop rows from the end
354 # drop rows from the end
355 del self.displayrows[self.datastarty-olddatastarty:]
355 del self.displayrows[self.datastarty-olddatastarty:]
356 # fetch new items
356 # fetch new items
357 for i in xrange(min(olddatastarty, self.datastarty+self.mainsizey)-1,
357 for i in xrange(min(olddatastarty, self.datastarty+self.mainsizey)-1,
358 self.datastarty-1, -1):
358 self.datastarty-1, -1):
359 try:
359 try:
360 row = self.getrow(i)
360 row = self.getrow(i)
361 except IndexError:
361 except IndexError:
362 # we didn't have enough objects to fill the screen
362 # we didn't have enough objects to fill the screen
363 break
363 break
364 self.displayrows.insert(0, row)
364 self.displayrows.insert(0, row)
365 else: # we did scroll down
365 else: # we did scroll down
366 # drop rows from the start
366 # drop rows from the start
367 del self.displayrows[:self.datastarty-olddatastarty]
367 del self.displayrows[:self.datastarty-olddatastarty]
368 # fetch new items
368 # fetch new items
369 for i in xrange(max(olddatastarty+self.mainsizey, self.datastarty),
369 for i in xrange(max(olddatastarty+self.mainsizey, self.datastarty),
370 self.datastarty+self.mainsizey):
370 self.datastarty+self.mainsizey):
371 try:
371 try:
372 row = self.getrow(i)
372 row = self.getrow(i)
373 except IndexError:
373 except IndexError:
374 # we didn't have enough objects to fill the screen
374 # we didn't have enough objects to fill the screen
375 break
375 break
376 self.displayrows.append(row)
376 self.displayrows.append(row)
377 self.calcwidths()
377 self.calcwidths()
378
378
379 # Make sure that the cursor didn't leave the data area horizontally
379 # Make sure that the cursor didn't leave the data area horizontally
380 if x < 0:
380 if x < 0:
381 x = 0
381 x = 0
382 elif x >= self.datasizex:
382 elif x >= self.datasizex:
383 x = max(0, self.datasizex-1)
383 x = max(0, self.datasizex-1)
384
384
385 # Make sure that the cursor stays on screen horizontally
385 # Make sure that the cursor stays on screen horizontally
386 if x < self.datastartx+scrollborderx:
386 if x < self.datastartx+scrollborderx:
387 self.datastartx = max(0, x-scrollborderx)
387 self.datastartx = max(0, x-scrollborderx)
388 elif x >= self.datastartx+self.mainsizex-scrollborderx:
388 elif x >= self.datastartx+self.mainsizex-scrollborderx:
389 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
389 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
390 self.datasizex-self.mainsizex))
390 self.datasizex-self.mainsizex))
391
391
392 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
392 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
393 self.browser.beep()
393 self.browser.beep()
394 else:
394 else:
395 self.curx = x
395 self.curx = x
396 self.cury = y
396 self.cury = y
397 self.calcdisplayattr()
397 self.calcdisplayattr()
398
398
399 def sort(self, key, reverse=False):
399 def sort(self, key, reverse=False):
400 """
400 """
401 Sort the currently list of items using the key function ``key``. If
401 Sort the currently list of items using the key function ``key``. If
402 ``reverse`` is true the sort order is reversed.
402 ``reverse`` is true the sort order is reversed.
403 """
403 """
404 curitem = self.items[self.cury] # Remember where the cursor is now
404 curitem = self.items[self.cury] # Remember where the cursor is now
405
405
406 # Sort items
406 # Sort items
407 def realkey(item):
407 def realkey(item):
408 return key(item.item)
408 return key(item.item)
409 self.items = ipipe.deque(sorted(self.items, key=realkey, reverse=reverse))
409 self.items = ipipe.deque(sorted(self.items, key=realkey, reverse=reverse))
410
410
411 # Find out where the object under the cursor went
411 # Find out where the object under the cursor went
412 cury = self.cury
412 cury = self.cury
413 for (i, item) in enumerate(self.items):
413 for (i, item) in enumerate(self.items):
414 if item is curitem:
414 if item is curitem:
415 cury = i
415 cury = i
416 break
416 break
417
417
418 self.moveto(self.curx, cury, refresh=True)
418 self.moveto(self.curx, cury, refresh=True)
419
419
420
420
421 class _CommandInput(object):
421 class _CommandInput(object):
422 keymap = Keymap()
422 keymap = Keymap()
423 keymap.register("left", curses.KEY_LEFT)
423 keymap.register("left", curses.KEY_LEFT)
424 keymap.register("right", curses.KEY_RIGHT)
424 keymap.register("right", curses.KEY_RIGHT)
425 keymap.register("home", curses.KEY_HOME, "\x01") # Ctrl-A
425 keymap.register("home", curses.KEY_HOME, "\x01") # Ctrl-A
426 keymap.register("end", curses.KEY_END, "\x05") # Ctrl-E
426 keymap.register("end", curses.KEY_END, "\x05") # Ctrl-E
427 # FIXME: What's happening here?
427 # FIXME: What's happening here?
428 keymap.register("backspace", curses.KEY_BACKSPACE, "\x08\x7f")
428 keymap.register("backspace", curses.KEY_BACKSPACE, "\x08\x7f")
429 keymap.register("delete", curses.KEY_DC)
429 keymap.register("delete", curses.KEY_DC)
430 keymap.register("delend", 0x0b) # Ctrl-K
430 keymap.register("delend", 0x0b) # Ctrl-K
431 keymap.register("execute", "\r\n")
431 keymap.register("execute", "\r\n")
432 keymap.register("up", curses.KEY_UP)
432 keymap.register("up", curses.KEY_UP)
433 keymap.register("down", curses.KEY_DOWN)
433 keymap.register("down", curses.KEY_DOWN)
434 keymap.register("incsearchup", curses.KEY_PPAGE)
434 keymap.register("incsearchup", curses.KEY_PPAGE)
435 keymap.register("incsearchdown", curses.KEY_NPAGE)
435 keymap.register("incsearchdown", curses.KEY_NPAGE)
436 keymap.register("exit", "\x18"), # Ctrl-X
436 keymap.register("exit", "\x18"), # Ctrl-X
437
437
438 def __init__(self, prompt):
438 def __init__(self, prompt):
439 self.prompt = prompt
439 self.prompt = prompt
440 self.history = []
440 self.history = []
441 self.maxhistory = 100
441 self.maxhistory = 100
442 self.input = ""
442 self.input = ""
443 self.curx = 0
443 self.curx = 0
444 self.cury = -1 # blank line
444 self.cury = -1 # blank line
445
445
446 def start(self):
446 def start(self):
447 self.input = ""
447 self.input = ""
448 self.curx = 0
448 self.curx = 0
449 self.cury = -1 # blank line
449 self.cury = -1 # blank line
450
450
451 def handlekey(self, browser, key):
451 def handlekey(self, browser, key):
452 cmdname = self.keymap.get(key, None)
452 cmdname = self.keymap.get(key, None)
453 if cmdname is not None:
453 if cmdname is not None:
454 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
454 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
455 if cmdfunc is not None:
455 if cmdfunc is not None:
456 return cmdfunc(browser)
456 return cmdfunc(browser)
457 curses.beep()
457 curses.beep()
458 elif key != -1:
458 elif key != -1:
459 try:
459 try:
460 char = chr(key)
460 char = chr(key)
461 except ValueError:
461 except ValueError:
462 curses.beep()
462 curses.beep()
463 else:
463 else:
464 return self.handlechar(browser, char)
464 return self.handlechar(browser, char)
465
465
466 def handlechar(self, browser, char):
466 def handlechar(self, browser, char):
467 self.input = self.input[:self.curx] + char + self.input[self.curx:]
467 self.input = self.input[:self.curx] + char + self.input[self.curx:]
468 self.curx += 1
468 self.curx += 1
469 return True
469 return True
470
470
471 def dohistory(self):
471 def dohistory(self):
472 self.history.insert(0, self.input)
472 self.history.insert(0, self.input)
473 del self.history[:-self.maxhistory]
473 del self.history[:-self.maxhistory]
474
474
475 def cmd_backspace(self, browser):
475 def cmd_backspace(self, browser):
476 if self.curx:
476 if self.curx:
477 self.input = self.input[:self.curx-1] + self.input[self.curx:]
477 self.input = self.input[:self.curx-1] + self.input[self.curx:]
478 self.curx -= 1
478 self.curx -= 1
479 return True
479 return True
480 else:
480 else:
481 curses.beep()
481 curses.beep()
482
482
483 def cmd_delete(self, browser):
483 def cmd_delete(self, browser):
484 if self.curx<len(self.input):
484 if self.curx<len(self.input):
485 self.input = self.input[:self.curx] + self.input[self.curx+1:]
485 self.input = self.input[:self.curx] + self.input[self.curx+1:]
486 return True
486 return True
487 else:
487 else:
488 curses.beep()
488 curses.beep()
489
489
490 def cmd_delend(self, browser):
490 def cmd_delend(self, browser):
491 if self.curx<len(self.input):
491 if self.curx<len(self.input):
492 self.input = self.input[:self.curx]
492 self.input = self.input[:self.curx]
493 return True
493 return True
494
494
495 def cmd_left(self, browser):
495 def cmd_left(self, browser):
496 if self.curx:
496 if self.curx:
497 self.curx -= 1
497 self.curx -= 1
498 return True
498 return True
499 else:
499 else:
500 curses.beep()
500 curses.beep()
501
501
502 def cmd_right(self, browser):
502 def cmd_right(self, browser):
503 if self.curx < len(self.input):
503 if self.curx < len(self.input):
504 self.curx += 1
504 self.curx += 1
505 return True
505 return True
506 else:
506 else:
507 curses.beep()
507 curses.beep()
508
508
509 def cmd_home(self, browser):
509 def cmd_home(self, browser):
510 if self.curx:
510 if self.curx:
511 self.curx = 0
511 self.curx = 0
512 return True
512 return True
513 else:
513 else:
514 curses.beep()
514 curses.beep()
515
515
516 def cmd_end(self, browser):
516 def cmd_end(self, browser):
517 if self.curx < len(self.input):
517 if self.curx < len(self.input):
518 self.curx = len(self.input)
518 self.curx = len(self.input)
519 return True
519 return True
520 else:
520 else:
521 curses.beep()
521 curses.beep()
522
522
523 def cmd_up(self, browser):
523 def cmd_up(self, browser):
524 if self.cury < len(self.history)-1:
524 if self.cury < len(self.history)-1:
525 self.cury += 1
525 self.cury += 1
526 self.input = self.history[self.cury]
526 self.input = self.history[self.cury]
527 self.curx = len(self.input)
527 self.curx = len(self.input)
528 return True
528 return True
529 else:
529 else:
530 curses.beep()
530 curses.beep()
531
531
532 def cmd_down(self, browser):
532 def cmd_down(self, browser):
533 if self.cury >= 0:
533 if self.cury >= 0:
534 self.cury -= 1
534 self.cury -= 1
535 if self.cury>=0:
535 if self.cury>=0:
536 self.input = self.history[self.cury]
536 self.input = self.history[self.cury]
537 else:
537 else:
538 self.input = ""
538 self.input = ""
539 self.curx = len(self.input)
539 self.curx = len(self.input)
540 return True
540 return True
541 else:
541 else:
542 curses.beep()
542 curses.beep()
543
543
544 def cmd_incsearchup(self, browser):
544 def cmd_incsearchup(self, browser):
545 prefix = self.input[:self.curx]
545 prefix = self.input[:self.curx]
546 cury = self.cury
546 cury = self.cury
547 while True:
547 while True:
548 cury += 1
548 cury += 1
549 if cury >= len(self.history):
549 if cury >= len(self.history):
550 break
550 break
551 if self.history[cury].startswith(prefix):
551 if self.history[cury].startswith(prefix):
552 self.input = self.history[cury]
552 self.input = self.history[cury]
553 self.cury = cury
553 self.cury = cury
554 return True
554 return True
555 curses.beep()
555 curses.beep()
556
556
557 def cmd_incsearchdown(self, browser):
557 def cmd_incsearchdown(self, browser):
558 prefix = self.input[:self.curx]
558 prefix = self.input[:self.curx]
559 cury = self.cury
559 cury = self.cury
560 while True:
560 while True:
561 cury -= 1
561 cury -= 1
562 if cury <= 0:
562 if cury <= 0:
563 break
563 break
564 if self.history[cury].startswith(prefix):
564 if self.history[cury].startswith(prefix):
565 self.input = self.history[self.cury]
565 self.input = self.history[self.cury]
566 self.cury = cury
566 self.cury = cury
567 return True
567 return True
568 curses.beep()
568 curses.beep()
569
569
570 def cmd_exit(self, browser):
570 def cmd_exit(self, browser):
571 browser.mode = "default"
571 browser.mode = "default"
572 return True
572 return True
573
573
574 def cmd_execute(self, browser):
574 def cmd_execute(self, browser):
575 raise NotImplementedError
575 raise NotImplementedError
576
576
577
577
578 class _CommandGoto(_CommandInput):
578 class _CommandGoto(_CommandInput):
579 def __init__(self):
579 def __init__(self):
580 _CommandInput.__init__(self, "goto object #")
580 _CommandInput.__init__(self, "goto object #")
581
581
582 def handlechar(self, browser, char):
582 def handlechar(self, browser, char):
583 # Only accept digits
583 # Only accept digits
584 if not "0" <= char <= "9":
584 if not "0" <= char <= "9":
585 curses.beep()
585 curses.beep()
586 else:
586 else:
587 return _CommandInput.handlechar(self, browser, char)
587 return _CommandInput.handlechar(self, browser, char)
588
588
589 def cmd_execute(self, browser):
589 def cmd_execute(self, browser):
590 level = browser.levels[-1]
590 level = browser.levels[-1]
591 if self.input:
591 if self.input:
592 self.dohistory()
592 self.dohistory()
593 level.moveto(level.curx, int(self.input))
593 level.moveto(level.curx, int(self.input))
594 browser.mode = "default"
594 browser.mode = "default"
595 return True
595 return True
596
596
597
597
598 class _CommandFind(_CommandInput):
598 class _CommandFind(_CommandInput):
599 def __init__(self):
599 def __init__(self):
600 _CommandInput.__init__(self, "find expression")
600 _CommandInput.__init__(self, "find expression")
601
601
602 def cmd_execute(self, browser):
602 def cmd_execute(self, browser):
603 level = browser.levels[-1]
603 level = browser.levels[-1]
604 if self.input:
604 if self.input:
605 self.dohistory()
605 self.dohistory()
606 while True:
606 while True:
607 cury = level.cury
607 cury = level.cury
608 level.moveto(level.curx, cury+1)
608 level.moveto(level.curx, cury+1)
609 if cury == level.cury:
609 if cury == level.cury:
610 curses.beep()
610 curses.beep()
611 break # hit end
611 break # hit end
612 item = level.items[level.cury].item
612 item = level.items[level.cury].item
613 try:
613 try:
614 globals = ipipe.getglobals(None)
614 globals = ipipe.getglobals(None)
615 if eval(self.input, globals, ipipe.AttrNamespace(item)):
615 if eval(self.input, globals, ipipe.AttrNamespace(item)):
616 break # found something
616 break # found something
617 except (KeyboardInterrupt, SystemExit):
617 except (KeyboardInterrupt, SystemExit):
618 raise
618 raise
619 except Exception, exc:
619 except Exception, exc:
620 browser.report(exc)
620 browser.report(exc)
621 curses.beep()
621 curses.beep()
622 break # break on error
622 break # break on error
623 browser.mode = "default"
623 browser.mode = "default"
624 return True
624 return True
625
625
626
626
627 class _CommandFindBackwards(_CommandInput):
627 class _CommandFindBackwards(_CommandInput):
628 def __init__(self):
628 def __init__(self):
629 _CommandInput.__init__(self, "find backwards expression")
629 _CommandInput.__init__(self, "find backwards expression")
630
630
631 def cmd_execute(self, browser):
631 def cmd_execute(self, browser):
632 level = browser.levels[-1]
632 level = browser.levels[-1]
633 if self.input:
633 if self.input:
634 self.dohistory()
634 self.dohistory()
635 while level.cury:
635 while level.cury:
636 level.moveto(level.curx, level.cury-1)
636 level.moveto(level.curx, level.cury-1)
637 item = level.items[level.cury].item
637 item = level.items[level.cury].item
638 try:
638 try:
639 globals = ipipe.getglobals(None)
639 globals = ipipe.getglobals(None)
640 if eval(self.input, globals, ipipe.AttrNamespace(item)):
640 if eval(self.input, globals, ipipe.AttrNamespace(item)):
641 break # found something
641 break # found something
642 except (KeyboardInterrupt, SystemExit):
642 except (KeyboardInterrupt, SystemExit):
643 raise
643 raise
644 except Exception, exc:
644 except Exception, exc:
645 browser.report(exc)
645 browser.report(exc)
646 curses.beep()
646 curses.beep()
647 break # break on error
647 break # break on error
648 else:
648 else:
649 curses.beep()
649 curses.beep()
650 browser.mode = "default"
650 browser.mode = "default"
651 return True
651 return True
652
652
653
653
654 class ibrowse(ipipe.Display):
654 class ibrowse(ipipe.Display):
655 # Show this many lines from the previous screen when paging horizontally
655 # Show this many lines from the previous screen when paging horizontally
656 pageoverlapx = 1
656 pageoverlapx = 1
657
657
658 # Show this many lines from the previous screen when paging vertically
658 # Show this many lines from the previous screen when paging vertically
659 pageoverlapy = 1
659 pageoverlapy = 1
660
660
661 # Start scrolling when the cursor is less than this number of columns
661 # Start scrolling when the cursor is less than this number of columns
662 # away from the left or right screen edge
662 # away from the left or right screen edge
663 scrollborderx = 10
663 scrollborderx = 10
664
664
665 # Start scrolling when the cursor is less than this number of lines
665 # Start scrolling when the cursor is less than this number of lines
666 # away from the top or bottom screen edge
666 # away from the top or bottom screen edge
667 scrollbordery = 5
667 scrollbordery = 5
668
668
669 # Accelerate by this factor when scrolling horizontally
669 # Accelerate by this factor when scrolling horizontally
670 acceleratex = 1.05
670 acceleratex = 1.05
671
671
672 # Accelerate by this factor when scrolling vertically
672 # Accelerate by this factor when scrolling vertically
673 acceleratey = 1.05
673 acceleratey = 1.05
674
674
675 # The maximum horizontal scroll speed
675 # The maximum horizontal scroll speed
676 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
676 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
677 maxspeedx = 0.5
677 maxspeedx = 0.5
678
678
679 # The maximum vertical scroll speed
679 # The maximum vertical scroll speed
680 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
680 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
681 maxspeedy = 0.5
681 maxspeedy = 0.5
682
682
683 # The maximum number of header lines for browser level
683 # The maximum number of header lines for browser level
684 # if the nesting is deeper, only the innermost levels are displayed
684 # if the nesting is deeper, only the innermost levels are displayed
685 maxheaders = 5
685 maxheaders = 5
686
686
687 # The approximate maximum length of a column entry
687 # The approximate maximum length of a column entry
688 maxattrlength = 200
688 maxattrlength = 200
689
689
690 # Styles for various parts of the GUI
690 # Styles for various parts of the GUI
691 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
691 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
692 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
692 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
693 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
693 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
694 style_colheader = astyle.Style.fromstr("blue:white:reverse")
694 style_colheader = astyle.Style.fromstr("blue:white:reverse")
695 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
695 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
696 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
696 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
697 style_number = astyle.Style.fromstr("blue:white:reverse")
697 style_number = astyle.Style.fromstr("blue:white:reverse")
698 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
698 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
699 style_sep = astyle.Style.fromstr("blue:black")
699 style_sep = astyle.Style.fromstr("blue:black")
700 style_data = astyle.Style.fromstr("white:black")
700 style_data = astyle.Style.fromstr("white:black")
701 style_datapad = astyle.Style.fromstr("blue:black:bold")
701 style_datapad = astyle.Style.fromstr("blue:black:bold")
702 style_footer = astyle.Style.fromstr("black:white")
702 style_footer = astyle.Style.fromstr("black:white")
703 style_report = astyle.Style.fromstr("white:black")
703 style_report = astyle.Style.fromstr("white:black")
704
704
705 # Column separator in header
705 # Column separator in header
706 headersepchar = "|"
706 headersepchar = "|"
707
707
708 # Character for padding data cell entries
708 # Character for padding data cell entries
709 datapadchar = "."
709 datapadchar = "."
710
710
711 # Column separator in data area
711 # Column separator in data area
712 datasepchar = "|"
712 datasepchar = "|"
713
713
714 # Character to use for "empty" cell (i.e. for non-existing attributes)
714 # Character to use for "empty" cell (i.e. for non-existing attributes)
715 nodatachar = "-"
715 nodatachar = "-"
716
716
717 # Prompts for modes that require keyboard input
717 # Prompts for modes that require keyboard input
718 prompts = {
718 prompts = {
719 "goto": _CommandGoto(),
719 "goto": _CommandGoto(),
720 "find": _CommandFind(),
720 "find": _CommandFind(),
721 "findbackwards": _CommandFindBackwards()
721 "findbackwards": _CommandFindBackwards()
722 }
722 }
723
723
724 # Maps curses key codes to "function" names
724 # Maps curses key codes to "function" names
725 keymap = Keymap()
725 keymap = Keymap()
726 keymap.register("quit", "q")
726 keymap.register("quit", "q")
727 keymap.register("up", curses.KEY_UP)
727 keymap.register("up", curses.KEY_UP)
728 keymap.register("down", curses.KEY_DOWN)
728 keymap.register("down", curses.KEY_DOWN)
729 keymap.register("pageup", curses.KEY_PPAGE)
729 keymap.register("pageup", curses.KEY_PPAGE)
730 keymap.register("pagedown", curses.KEY_NPAGE)
730 keymap.register("pagedown", curses.KEY_NPAGE)
731 keymap.register("left", curses.KEY_LEFT)
731 keymap.register("left", curses.KEY_LEFT)
732 keymap.register("right", curses.KEY_RIGHT)
732 keymap.register("right", curses.KEY_RIGHT)
733 keymap.register("home", curses.KEY_HOME, "\x01")
733 keymap.register("home", curses.KEY_HOME, "\x01")
734 keymap.register("end", curses.KEY_END, "\x05")
734 keymap.register("end", curses.KEY_END, "\x05")
735 keymap.register("prevattr", "<\x1b")
735 keymap.register("prevattr", "<\x1b")
736 keymap.register("nextattr", ">\t")
736 keymap.register("nextattr", ">\t")
737 keymap.register("pick", "p")
737 keymap.register("pick", "p")
738 keymap.register("pickattr", "P")
738 keymap.register("pickattr", "P")
739 keymap.register("pickallattrs", "C")
739 keymap.register("pickallattrs", "C")
740 keymap.register("pickmarked", "m")
740 keymap.register("pickmarked", "m")
741 keymap.register("pickmarkedattr", "M")
741 keymap.register("pickmarkedattr", "M")
742 keymap.register("hideattr", "h")
742 keymap.register("hideattr", "h")
743 keymap.register("unhideattrs", "H")
743 keymap.register("unhideattrs", "H")
744 keymap.register("help", "?")
744 keymap.register("help", "?")
745 keymap.register("enter", "eenterdefault", "\r\n")
745 keymap.register("enter", "\r\n")
746 keymap.register("enterattr", "E")
746 keymap.register("enterattr", "E")
747 # FIXME: What's happening here?
747 # FIXME: What's happening here?
748 keymap.register("leave", curses.KEY_BACKSPACE, "x\x08\x7f")
748 keymap.register("leave", curses.KEY_BACKSPACE, "x\x08\x7f")
749 keymap.register("detail", "d")
749 keymap.register("detail", "d")
750 keymap.register("detailattr", "D")
750 keymap.register("detailattr", "D")
751 keymap.register("tooglemark", " ")
751 keymap.register("tooglemark", " ")
752 keymap.register("markrange", "r")
752 keymap.register("markrange", "r")
753 keymap.register("sortattrasc", "v")
753 keymap.register("sortattrasc", "v")
754 keymap.register("sortattrdesc", "V")
754 keymap.register("sortattrdesc", "V")
755 keymap.register("goto", "g")
755 keymap.register("goto", "g")
756 keymap.register("find", "f")
756 keymap.register("find", "f")
757 keymap.register("findbackwards", "b")
757 keymap.register("findbackwards", "b")
758
758
759 def __init__(self, *attrs):
759 def __init__(self, *attrs):
760 """
760 """
761 Create a new browser. If ``attrs`` is not empty, it is the list
761 Create a new browser. If ``attrs`` is not empty, it is the list
762 of attributes that will be displayed in the browser, otherwise
762 of attributes that will be displayed in the browser, otherwise
763 these will be determined by the objects on screen.
763 these will be determined by the objects on screen.
764 """
764 """
765 self.attrs = attrs
765 self.attrs = attrs
766
766
767 # Stack of browser levels
767 # Stack of browser levels
768 self.levels = []
768 self.levels = []
769 # how many colums to scroll (Changes when accelerating)
769 # how many colums to scroll (Changes when accelerating)
770 self.stepx = 1.
770 self.stepx = 1.
771
771
772 # how many rows to scroll (Changes when accelerating)
772 # how many rows to scroll (Changes when accelerating)
773 self.stepy = 1.
773 self.stepy = 1.
774
774
775 # Beep on the edges of the data area? (Will be set to ``False``
775 # Beep on the edges of the data area? (Will be set to ``False``
776 # once the cursor hits the edge of the screen, so we don't get
776 # once the cursor hits the edge of the screen, so we don't get
777 # multiple beeps).
777 # multiple beeps).
778 self._dobeep = True
778 self._dobeep = True
779
779
780 # Cache for registered ``curses`` colors and styles.
780 # Cache for registered ``curses`` colors and styles.
781 self._styles = {}
781 self._styles = {}
782 self._colors = {}
782 self._colors = {}
783 self._maxcolor = 1
783 self._maxcolor = 1
784
784
785 # How many header lines do we want to paint (the numbers of levels
785 # How many header lines do we want to paint (the numbers of levels
786 # we have, but with an upper bound)
786 # we have, but with an upper bound)
787 self._headerlines = 1
787 self._headerlines = 1
788
788
789 # Index of first header line
789 # Index of first header line
790 self._firstheaderline = 0
790 self._firstheaderline = 0
791
791
792 # curses window
792 # curses window
793 self.scr = None
793 self.scr = None
794 # report in the footer line (error, executed command etc.)
794 # report in the footer line (error, executed command etc.)
795 self._report = None
795 self._report = None
796
796
797 # value to be returned to the caller (set by commands)
797 # value to be returned to the caller (set by commands)
798 self.returnvalue = None
798 self.returnvalue = None
799
799
800 # The mode the browser is in
800 # The mode the browser is in
801 # e.g. normal browsing or entering an argument for a command
801 # e.g. normal browsing or entering an argument for a command
802 self.mode = "default"
802 self.mode = "default"
803
803
804 # set by the SIGWINCH signal handler
804 # set by the SIGWINCH signal handler
805 self.resized = False
805 self.resized = False
806
806
807 def nextstepx(self, step):
807 def nextstepx(self, step):
808 """
808 """
809 Accelerate horizontally.
809 Accelerate horizontally.
810 """
810 """
811 return max(1., min(step*self.acceleratex,
811 return max(1., min(step*self.acceleratex,
812 self.maxspeedx*self.levels[-1].mainsizex))
812 self.maxspeedx*self.levels[-1].mainsizex))
813
813
814 def nextstepy(self, step):
814 def nextstepy(self, step):
815 """
815 """
816 Accelerate vertically.
816 Accelerate vertically.
817 """
817 """
818 return max(1., min(step*self.acceleratey,
818 return max(1., min(step*self.acceleratey,
819 self.maxspeedy*self.levels[-1].mainsizey))
819 self.maxspeedy*self.levels[-1].mainsizey))
820
820
821 def getstyle(self, style):
821 def getstyle(self, style):
822 """
822 """
823 Register the ``style`` with ``curses`` or get it from the cache,
823 Register the ``style`` with ``curses`` or get it from the cache,
824 if it has been registered before.
824 if it has been registered before.
825 """
825 """
826 try:
826 try:
827 return self._styles[style.fg, style.bg, style.attrs]
827 return self._styles[style.fg, style.bg, style.attrs]
828 except KeyError:
828 except KeyError:
829 attrs = 0
829 attrs = 0
830 for b in astyle.A2CURSES:
830 for b in astyle.A2CURSES:
831 if style.attrs & b:
831 if style.attrs & b:
832 attrs |= astyle.A2CURSES[b]
832 attrs |= astyle.A2CURSES[b]
833 try:
833 try:
834 color = self._colors[style.fg, style.bg]
834 color = self._colors[style.fg, style.bg]
835 except KeyError:
835 except KeyError:
836 curses.init_pair(
836 curses.init_pair(
837 self._maxcolor,
837 self._maxcolor,
838 astyle.COLOR2CURSES[style.fg],
838 astyle.COLOR2CURSES[style.fg],
839 astyle.COLOR2CURSES[style.bg]
839 astyle.COLOR2CURSES[style.bg]
840 )
840 )
841 color = curses.color_pair(self._maxcolor)
841 color = curses.color_pair(self._maxcolor)
842 self._colors[style.fg, style.bg] = color
842 self._colors[style.fg, style.bg] = color
843 self._maxcolor += 1
843 self._maxcolor += 1
844 c = color | attrs
844 c = color | attrs
845 self._styles[style.fg, style.bg, style.attrs] = c
845 self._styles[style.fg, style.bg, style.attrs] = c
846 return c
846 return c
847
847
848 def addstr(self, y, x, begx, endx, text, style):
848 def addstr(self, y, x, begx, endx, text, style):
849 """
849 """
850 A version of ``curses.addstr()`` that can handle ``x`` coordinates
850 A version of ``curses.addstr()`` that can handle ``x`` coordinates
851 that are outside the screen.
851 that are outside the screen.
852 """
852 """
853 text2 = text[max(0, begx-x):max(0, endx-x)]
853 text2 = text[max(0, begx-x):max(0, endx-x)]
854 if text2:
854 if text2:
855 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
855 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
856 return len(text)
856 return len(text)
857
857
858 def addchr(self, y, x, begx, endx, c, l, style):
858 def addchr(self, y, x, begx, endx, c, l, style):
859 x0 = max(x, begx)
859 x0 = max(x, begx)
860 x1 = min(x+l, endx)
860 x1 = min(x+l, endx)
861 if x1>x0:
861 if x1>x0:
862 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
862 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
863 return l
863 return l
864
864
865 def _calcheaderlines(self, levels):
865 def _calcheaderlines(self, levels):
866 # Calculate how many headerlines do we have to display, if we have
866 # Calculate how many headerlines do we have to display, if we have
867 # ``levels`` browser levels
867 # ``levels`` browser levels
868 if levels is None:
868 if levels is None:
869 levels = len(self.levels)
869 levels = len(self.levels)
870 self._headerlines = min(self.maxheaders, levels)
870 self._headerlines = min(self.maxheaders, levels)
871 self._firstheaderline = levels-self._headerlines
871 self._firstheaderline = levels-self._headerlines
872
872
873 def getstylehere(self, style):
873 def getstylehere(self, style):
874 """
874 """
875 Return a style for displaying the original style ``style``
875 Return a style for displaying the original style ``style``
876 in the row the cursor is on.
876 in the row the cursor is on.
877 """
877 """
878 return astyle.Style(style.fg, astyle.COLOR_BLUE, style.attrs | astyle.A_BOLD)
878 return astyle.Style(style.fg, astyle.COLOR_BLUE, style.attrs | astyle.A_BOLD)
879
879
880 def report(self, msg):
880 def report(self, msg):
881 """
881 """
882 Store the message ``msg`` for display below the footer line. This
882 Store the message ``msg`` for display below the footer line. This
883 will be displayed as soon as the screen is redrawn.
883 will be displayed as soon as the screen is redrawn.
884 """
884 """
885 self._report = msg
885 self._report = msg
886
886
887 def enter(self, item, *attrs):
887 def enter(self, item, *attrs):
888 """
888 """
889 Enter the object ``item``. If ``attrs`` is specified, it will be used
889 Enter the object ``item``. If ``attrs`` is specified, it will be used
890 as a fixed list of attributes to display.
890 as a fixed list of attributes to display.
891 """
891 """
892 try:
892 try:
893 iterator = ipipe.xiter(item)
893 iterator = ipipe.xiter(item)
894 except (KeyboardInterrupt, SystemExit):
894 except (KeyboardInterrupt, SystemExit):
895 raise
895 raise
896 except Exception, exc:
896 except Exception, exc:
897 curses.beep()
897 curses.beep()
898 self.report(exc)
898 self.report(exc)
899 else:
899 else:
900 self._calcheaderlines(len(self.levels)+1)
900 self._calcheaderlines(len(self.levels)+1)
901 level = _BrowserLevel(
901 level = _BrowserLevel(
902 self,
902 self,
903 item,
903 item,
904 iterator,
904 iterator,
905 self.scrsizey-1-self._headerlines-2,
905 self.scrsizey-1-self._headerlines-2,
906 *attrs
906 *attrs
907 )
907 )
908 self.levels.append(level)
908 self.levels.append(level)
909
909
910 def startkeyboardinput(self, mode):
910 def startkeyboardinput(self, mode):
911 """
911 """
912 Enter mode ``mode``, which requires keyboard input.
912 Enter mode ``mode``, which requires keyboard input.
913 """
913 """
914 self.mode = mode
914 self.mode = mode
915 self.prompts[mode].start()
915 self.prompts[mode].start()
916
916
917 def keylabel(self, keycode):
917 def keylabel(self, keycode):
918 """
918 """
919 Return a pretty name for the ``curses`` key ``keycode`` (used in the
919 Return a pretty name for the ``curses`` key ``keycode`` (used in the
920 help screen and in reports about unassigned keys).
920 help screen and in reports about unassigned keys).
921 """
921 """
922 if keycode <= 0xff:
922 if keycode <= 0xff:
923 specialsnames = {
923 specialsnames = {
924 ord("\n"): "RETURN",
924 ord("\n"): "RETURN",
925 ord(" "): "SPACE",
925 ord(" "): "SPACE",
926 ord("\t"): "TAB",
926 ord("\t"): "TAB",
927 ord("\x7f"): "DELETE",
927 ord("\x7f"): "DELETE",
928 ord("\x08"): "BACKSPACE",
928 ord("\x08"): "BACKSPACE",
929 }
929 }
930 if keycode in specialsnames:
930 if keycode in specialsnames:
931 return specialsnames[keycode]
931 return specialsnames[keycode]
932 elif 0x00 < keycode < 0x20:
932 elif 0x00 < keycode < 0x20:
933 return "CTRL-%s" % chr(keycode + 64)
933 return "CTRL-%s" % chr(keycode + 64)
934 return repr(chr(keycode))
934 return repr(chr(keycode))
935 for name in dir(curses):
935 for name in dir(curses):
936 if name.startswith("KEY_") and getattr(curses, name) == keycode:
936 if name.startswith("KEY_") and getattr(curses, name) == keycode:
937 return name
937 return name
938 return str(keycode)
938 return str(keycode)
939
939
940 def beep(self, force=False):
940 def beep(self, force=False):
941 if force or self._dobeep:
941 if force or self._dobeep:
942 curses.beep()
942 curses.beep()
943 # don't beep again (as long as the same key is pressed)
943 # don't beep again (as long as the same key is pressed)
944 self._dobeep = False
944 self._dobeep = False
945
945
946 def cmd_up(self):
946 def cmd_up(self):
947 """
947 """
948 Move the cursor to the previous row.
948 Move the cursor to the previous row.
949 """
949 """
950 level = self.levels[-1]
950 level = self.levels[-1]
951 self.report("up")
951 self.report("up")
952 level.moveto(level.curx, level.cury-self.stepy)
952 level.moveto(level.curx, level.cury-self.stepy)
953
953
954 def cmd_down(self):
954 def cmd_down(self):
955 """
955 """
956 Move the cursor to the next row.
956 Move the cursor to the next row.
957 """
957 """
958 level = self.levels[-1]
958 level = self.levels[-1]
959 self.report("down")
959 self.report("down")
960 level.moveto(level.curx, level.cury+self.stepy)
960 level.moveto(level.curx, level.cury+self.stepy)
961
961
962 def cmd_pageup(self):
962 def cmd_pageup(self):
963 """
963 """
964 Move the cursor up one page.
964 Move the cursor up one page.
965 """
965 """
966 level = self.levels[-1]
966 level = self.levels[-1]
967 self.report("page up")
967 self.report("page up")
968 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
968 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
969
969
970 def cmd_pagedown(self):
970 def cmd_pagedown(self):
971 """
971 """
972 Move the cursor down one page.
972 Move the cursor down one page.
973 """
973 """
974 level = self.levels[-1]
974 level = self.levels[-1]
975 self.report("page down")
975 self.report("page down")
976 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
976 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
977
977
978 def cmd_left(self):
978 def cmd_left(self):
979 """
979 """
980 Move the cursor left.
980 Move the cursor left.
981 """
981 """
982 level = self.levels[-1]
982 level = self.levels[-1]
983 self.report("left")
983 self.report("left")
984 level.moveto(level.curx-self.stepx, level.cury)
984 level.moveto(level.curx-self.stepx, level.cury)
985
985
986 def cmd_right(self):
986 def cmd_right(self):
987 """
987 """
988 Move the cursor right.
988 Move the cursor right.
989 """
989 """
990 level = self.levels[-1]
990 level = self.levels[-1]
991 self.report("right")
991 self.report("right")
992 level.moveto(level.curx+self.stepx, level.cury)
992 level.moveto(level.curx+self.stepx, level.cury)
993
993
994 def cmd_home(self):
994 def cmd_home(self):
995 """
995 """
996 Move the cursor to the first column.
996 Move the cursor to the first column.
997 """
997 """
998 level = self.levels[-1]
998 level = self.levels[-1]
999 self.report("home")
999 self.report("home")
1000 level.moveto(0, level.cury)
1000 level.moveto(0, level.cury)
1001
1001
1002 def cmd_end(self):
1002 def cmd_end(self):
1003 """
1003 """
1004 Move the cursor to the last column.
1004 Move the cursor to the last column.
1005 """
1005 """
1006 level = self.levels[-1]
1006 level = self.levels[-1]
1007 self.report("end")
1007 self.report("end")
1008 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
1008 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
1009
1009
1010 def cmd_prevattr(self):
1010 def cmd_prevattr(self):
1011 """
1011 """
1012 Move the cursor one attribute column to the left.
1012 Move the cursor one attribute column to the left.
1013 """
1013 """
1014 level = self.levels[-1]
1014 level = self.levels[-1]
1015 if level.displayattr[0] is None or level.displayattr[0] == 0:
1015 if level.displayattr[0] is None or level.displayattr[0] == 0:
1016 self.beep()
1016 self.beep()
1017 else:
1017 else:
1018 self.report("prevattr")
1018 self.report("prevattr")
1019 pos = 0
1019 pos = 0
1020 for (i, attrname) in enumerate(level.displayattrs):
1020 for (i, attrname) in enumerate(level.displayattrs):
1021 if i == level.displayattr[0]-1:
1021 if i == level.displayattr[0]-1:
1022 break
1022 break
1023 pos += level.colwidths[attrname] + 1
1023 pos += level.colwidths[attrname] + 1
1024 level.moveto(pos, level.cury)
1024 level.moveto(pos, level.cury)
1025
1025
1026 def cmd_nextattr(self):
1026 def cmd_nextattr(self):
1027 """
1027 """
1028 Move the cursor one attribute column to the right.
1028 Move the cursor one attribute column to the right.
1029 """
1029 """
1030 level = self.levels[-1]
1030 level = self.levels[-1]
1031 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
1031 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
1032 self.beep()
1032 self.beep()
1033 else:
1033 else:
1034 self.report("nextattr")
1034 self.report("nextattr")
1035 pos = 0
1035 pos = 0
1036 for (i, attrname) in enumerate(level.displayattrs):
1036 for (i, attrname) in enumerate(level.displayattrs):
1037 if i == level.displayattr[0]+1:
1037 if i == level.displayattr[0]+1:
1038 break
1038 break
1039 pos += level.colwidths[attrname] + 1
1039 pos += level.colwidths[attrname] + 1
1040 level.moveto(pos, level.cury)
1040 level.moveto(pos, level.cury)
1041
1041
1042 def cmd_pick(self):
1042 def cmd_pick(self):
1043 """
1043 """
1044 'Pick' the object under the cursor (i.e. the row the cursor is on).
1044 'Pick' the object under the cursor (i.e. the row the cursor is on).
1045 This leaves the browser and returns the picked object to the caller.
1045 This leaves the browser and returns the picked object to the caller.
1046 (In IPython this object will be available as the ``_`` variable.)
1046 (In IPython this object will be available as the ``_`` variable.)
1047 """
1047 """
1048 level = self.levels[-1]
1048 level = self.levels[-1]
1049 self.returnvalue = level.items[level.cury].item
1049 self.returnvalue = level.items[level.cury].item
1050 return True
1050 return True
1051
1051
1052 def cmd_pickattr(self):
1052 def cmd_pickattr(self):
1053 """
1053 """
1054 'Pick' the attribute under the cursor (i.e. the row/column the
1054 'Pick' the attribute under the cursor (i.e. the row/column the
1055 cursor is on).
1055 cursor is on).
1056 """
1056 """
1057 level = self.levels[-1]
1057 level = self.levels[-1]
1058 attr = level.displayattr[1]
1058 attr = level.displayattr[1]
1059 if attr is ipipe.noitem:
1059 if attr is ipipe.noitem:
1060 curses.beep()
1060 curses.beep()
1061 self.report(CommandError("no column under cursor"))
1061 self.report(CommandError("no column under cursor"))
1062 return
1062 return
1063 value = attr.value(level.items[level.cury].item)
1063 value = attr.value(level.items[level.cury].item)
1064 if value is ipipe.noitem:
1064 if value is ipipe.noitem:
1065 curses.beep()
1065 curses.beep()
1066 self.report(AttributeError(attr.name()))
1066 self.report(AttributeError(attr.name()))
1067 else:
1067 else:
1068 self.returnvalue = value
1068 self.returnvalue = value
1069 return True
1069 return True
1070
1070
1071 def cmd_pickallattrs(self):
1071 def cmd_pickallattrs(self):
1072 """
1072 """
1073 Pick' the complete column under the cursor (i.e. the attribute under
1073 Pick' the complete column under the cursor (i.e. the attribute under
1074 the cursor) from all currently fetched objects. These attributes
1074 the cursor) from all currently fetched objects. These attributes
1075 will be returned as a list.
1075 will be returned as a list.
1076 """
1076 """
1077 level = self.levels[-1]
1077 level = self.levels[-1]
1078 attr = level.displayattr[1]
1078 attr = level.displayattr[1]
1079 if attr is ipipe.noitem:
1079 if attr is ipipe.noitem:
1080 curses.beep()
1080 curses.beep()
1081 self.report(CommandError("no column under cursor"))
1081 self.report(CommandError("no column under cursor"))
1082 return
1082 return
1083 result = []
1083 result = []
1084 for cache in level.items:
1084 for cache in level.items:
1085 value = attr.value(cache.item)
1085 value = attr.value(cache.item)
1086 if value is not ipipe.noitem:
1086 if value is not ipipe.noitem:
1087 result.append(value)
1087 result.append(value)
1088 self.returnvalue = result
1088 self.returnvalue = result
1089 return True
1089 return True
1090
1090
1091 def cmd_pickmarked(self):
1091 def cmd_pickmarked(self):
1092 """
1092 """
1093 'Pick' marked objects. Marked objects will be returned as a list.
1093 'Pick' marked objects. Marked objects will be returned as a list.
1094 """
1094 """
1095 level = self.levels[-1]
1095 level = self.levels[-1]
1096 self.returnvalue = [cache.item for cache in level.items if cache.marked]
1096 self.returnvalue = [cache.item for cache in level.items if cache.marked]
1097 return True
1097 return True
1098
1098
1099 def cmd_pickmarkedattr(self):
1099 def cmd_pickmarkedattr(self):
1100 """
1100 """
1101 'Pick' the attribute under the cursor from all marked objects
1101 'Pick' the attribute under the cursor from all marked objects
1102 (This returns a list).
1102 (This returns a list).
1103 """
1103 """
1104
1104
1105 level = self.levels[-1]
1105 level = self.levels[-1]
1106 attr = level.displayattr[1]
1106 attr = level.displayattr[1]
1107 if attr is ipipe.noitem:
1107 if attr is ipipe.noitem:
1108 curses.beep()
1108 curses.beep()
1109 self.report(CommandError("no column under cursor"))
1109 self.report(CommandError("no column under cursor"))
1110 return
1110 return
1111 result = []
1111 result = []
1112 for cache in level.items:
1112 for cache in level.items:
1113 if cache.marked:
1113 if cache.marked:
1114 value = attr.value(cache.item)
1114 value = attr.value(cache.item)
1115 if value is not ipipe.noitem:
1115 if value is not ipipe.noitem:
1116 result.append(value)
1116 result.append(value)
1117 self.returnvalue = result
1117 self.returnvalue = result
1118 return True
1118 return True
1119
1119
1120 def cmd_markrange(self):
1120 def cmd_markrange(self):
1121 """
1121 """
1122 Mark all objects from the last marked object before the current cursor
1122 Mark all objects from the last marked object before the current cursor
1123 position to the cursor position.
1123 position to the cursor position.
1124 """
1124 """
1125 level = self.levels[-1]
1125 level = self.levels[-1]
1126 self.report("markrange")
1126 self.report("markrange")
1127 start = None
1127 start = None
1128 if level.items:
1128 if level.items:
1129 for i in xrange(level.cury, -1, -1):
1129 for i in xrange(level.cury, -1, -1):
1130 if level.items[i].marked:
1130 if level.items[i].marked:
1131 start = i
1131 start = i
1132 break
1132 break
1133 if start is None:
1133 if start is None:
1134 self.report(CommandError("no mark before cursor"))
1134 self.report(CommandError("no mark before cursor"))
1135 curses.beep()
1135 curses.beep()
1136 else:
1136 else:
1137 for i in xrange(start, level.cury+1):
1137 for i in xrange(start, level.cury+1):
1138 cache = level.items[i]
1138 cache = level.items[i]
1139 if not cache.marked:
1139 if not cache.marked:
1140 cache.marked = True
1140 cache.marked = True
1141 level.marked += 1
1141 level.marked += 1
1142
1142
1143 def cmd_enter(self):
1143 def cmd_enter(self):
1144 """
1144 """
1145 Enter the object under the cursor. (what this mean depends on the object
1145 Enter the object under the cursor. (what this mean depends on the object
1146 itself (i.e. how it implements iteration). This opens a new browser 'level'.
1146 itself (i.e. how it implements iteration). This opens a new browser 'level'.
1147 """
1147 """
1148 level = self.levels[-1]
1148 level = self.levels[-1]
1149 try:
1149 try:
1150 item = level.items[level.cury].item
1150 item = level.items[level.cury].item
1151 except IndexError:
1151 except IndexError:
1152 self.report(CommandError("No object"))
1152 self.report(CommandError("No object"))
1153 curses.beep()
1153 curses.beep()
1154 else:
1154 else:
1155 self.report("entering object...")
1155 self.report("entering object...")
1156 self.enter(item)
1156 self.enter(item)
1157
1157
1158 def cmd_leave(self):
1158 def cmd_leave(self):
1159 """
1159 """
1160 Leave the current browser level and go back to the previous one.
1160 Leave the current browser level and go back to the previous one.
1161 """
1161 """
1162 self.report("leave")
1162 self.report("leave")
1163 if len(self.levels) > 1:
1163 if len(self.levels) > 1:
1164 self._calcheaderlines(len(self.levels)-1)
1164 self._calcheaderlines(len(self.levels)-1)
1165 self.levels.pop(-1)
1165 self.levels.pop(-1)
1166 else:
1166 else:
1167 self.report(CommandError("This is the last level"))
1167 self.report(CommandError("This is the last level"))
1168 curses.beep()
1168 curses.beep()
1169
1169
1170 def cmd_enterattr(self):
1170 def cmd_enterattr(self):
1171 """
1171 """
1172 Enter the attribute under the cursor.
1172 Enter the attribute under the cursor.
1173 """
1173 """
1174 level = self.levels[-1]
1174 level = self.levels[-1]
1175 attr = level.displayattr[1]
1175 attr = level.displayattr[1]
1176 if attr is ipipe.noitem:
1176 if attr is ipipe.noitem:
1177 curses.beep()
1177 curses.beep()
1178 self.report(CommandError("no column under cursor"))
1178 self.report(CommandError("no column under cursor"))
1179 return
1179 return
1180 try:
1180 try:
1181 item = level.items[level.cury].item
1181 item = level.items[level.cury].item
1182 except IndexError:
1182 except IndexError:
1183 self.report(CommandError("No object"))
1183 self.report(CommandError("No object"))
1184 curses.beep()
1184 curses.beep()
1185 else:
1185 else:
1186 value = attr.value(item)
1186 value = attr.value(item)
1187 name = attr.name()
1187 name = attr.name()
1188 if value is ipipe.noitem:
1188 if value is ipipe.noitem:
1189 self.report(AttributeError(name))
1189 self.report(AttributeError(name))
1190 else:
1190 else:
1191 self.report("entering object attribute %s..." % name)
1191 self.report("entering object attribute %s..." % name)
1192 self.enter(value)
1192 self.enter(value)
1193
1193
1194 def cmd_detail(self):
1194 def cmd_detail(self):
1195 """
1195 """
1196 Show a detail view of the object under the cursor. This shows the
1196 Show a detail view of the object under the cursor. This shows the
1197 name, type, doc string and value of the object attributes (and it
1197 name, type, doc string and value of the object attributes (and it
1198 might show more attributes than in the list view, depending on
1198 might show more attributes than in the list view, depending on
1199 the object).
1199 the object).
1200 """
1200 """
1201 level = self.levels[-1]
1201 level = self.levels[-1]
1202 try:
1202 try:
1203 item = level.items[level.cury].item
1203 item = level.items[level.cury].item
1204 except IndexError:
1204 except IndexError:
1205 self.report(CommandError("No object"))
1205 self.report(CommandError("No object"))
1206 curses.beep()
1206 curses.beep()
1207 else:
1207 else:
1208 self.report("entering detail view for object...")
1208 self.report("entering detail view for object...")
1209 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1209 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1210 self.enter(attrs)
1210 self.enter(attrs)
1211
1211
1212 def cmd_detailattr(self):
1212 def cmd_detailattr(self):
1213 """
1213 """
1214 Show a detail view of the attribute under the cursor.
1214 Show a detail view of the attribute under the cursor.
1215 """
1215 """
1216 level = self.levels[-1]
1216 level = self.levels[-1]
1217 attr = level.displayattr[1]
1217 attr = level.displayattr[1]
1218 if attr is ipipe.noitem:
1218 if attr is ipipe.noitem:
1219 curses.beep()
1219 curses.beep()
1220 self.report(CommandError("no attribute"))
1220 self.report(CommandError("no attribute"))
1221 return
1221 return
1222 try:
1222 try:
1223 item = level.items[level.cury].item
1223 item = level.items[level.cury].item
1224 except IndexError:
1224 except IndexError:
1225 self.report(CommandError("No object"))
1225 self.report(CommandError("No object"))
1226 curses.beep()
1226 curses.beep()
1227 else:
1227 else:
1228 try:
1228 try:
1229 item = attr.value(item)
1229 item = attr.value(item)
1230 except (KeyboardInterrupt, SystemExit):
1230 except (KeyboardInterrupt, SystemExit):
1231 raise
1231 raise
1232 except Exception, exc:
1232 except Exception, exc:
1233 self.report(exc)
1233 self.report(exc)
1234 else:
1234 else:
1235 self.report("entering detail view for attribute %s..." % attr.name())
1235 self.report("entering detail view for attribute %s..." % attr.name())
1236 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1236 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1237 self.enter(attrs)
1237 self.enter(attrs)
1238
1238
1239 def cmd_tooglemark(self):
1239 def cmd_tooglemark(self):
1240 """
1240 """
1241 Mark/unmark the object under the cursor. Marked objects have a '!'
1241 Mark/unmark the object under the cursor. Marked objects have a '!'
1242 after the row number).
1242 after the row number).
1243 """
1243 """
1244 level = self.levels[-1]
1244 level = self.levels[-1]
1245 self.report("toggle mark")
1245 self.report("toggle mark")
1246 try:
1246 try:
1247 item = level.items[level.cury]
1247 item = level.items[level.cury]
1248 except IndexError: # no items?
1248 except IndexError: # no items?
1249 pass
1249 pass
1250 else:
1250 else:
1251 if item.marked:
1251 if item.marked:
1252 item.marked = False
1252 item.marked = False
1253 level.marked -= 1
1253 level.marked -= 1
1254 else:
1254 else:
1255 item.marked = True
1255 item.marked = True
1256 level.marked += 1
1256 level.marked += 1
1257
1257
1258 def cmd_sortattrasc(self):
1258 def cmd_sortattrasc(self):
1259 """
1259 """
1260 Sort the objects (in ascending order) using the attribute under
1260 Sort the objects (in ascending order) using the attribute under
1261 the cursor as the sort key.
1261 the cursor as the sort key.
1262 """
1262 """
1263 level = self.levels[-1]
1263 level = self.levels[-1]
1264 attr = level.displayattr[1]
1264 attr = level.displayattr[1]
1265 if attr is ipipe.noitem:
1265 if attr is ipipe.noitem:
1266 curses.beep()
1266 curses.beep()
1267 self.report(CommandError("no column under cursor"))
1267 self.report(CommandError("no column under cursor"))
1268 return
1268 return
1269 self.report("sort by %s (ascending)" % attr.name())
1269 self.report("sort by %s (ascending)" % attr.name())
1270 def key(item):
1270 def key(item):
1271 try:
1271 try:
1272 return attr.value(item)
1272 return attr.value(item)
1273 except (KeyboardInterrupt, SystemExit):
1273 except (KeyboardInterrupt, SystemExit):
1274 raise
1274 raise
1275 except Exception:
1275 except Exception:
1276 return None
1276 return None
1277 level.sort(key)
1277 level.sort(key)
1278
1278
1279 def cmd_sortattrdesc(self):
1279 def cmd_sortattrdesc(self):
1280 """
1280 """
1281 Sort the objects (in descending order) using the attribute under
1281 Sort the objects (in descending order) using the attribute under
1282 the cursor as the sort key.
1282 the cursor as the sort key.
1283 """
1283 """
1284 level = self.levels[-1]
1284 level = self.levels[-1]
1285 attr = level.displayattr[1]
1285 attr = level.displayattr[1]
1286 if attr is ipipe.noitem:
1286 if attr is ipipe.noitem:
1287 curses.beep()
1287 curses.beep()
1288 self.report(CommandError("no column under cursor"))
1288 self.report(CommandError("no column under cursor"))
1289 return
1289 return
1290 self.report("sort by %s (descending)" % attr.name())
1290 self.report("sort by %s (descending)" % attr.name())
1291 def key(item):
1291 def key(item):
1292 try:
1292 try:
1293 return attr.value(item)
1293 return attr.value(item)
1294 except (KeyboardInterrupt, SystemExit):
1294 except (KeyboardInterrupt, SystemExit):
1295 raise
1295 raise
1296 except Exception:
1296 except Exception:
1297 return None
1297 return None
1298 level.sort(key, reverse=True)
1298 level.sort(key, reverse=True)
1299
1299
1300 def cmd_hideattr(self):
1300 def cmd_hideattr(self):
1301 """
1301 """
1302 Hide the attribute under the cursor.
1302 Hide the attribute under the cursor.
1303 """
1303 """
1304 level = self.levels[-1]
1304 level = self.levels[-1]
1305 if level.displayattr[0] is None:
1305 if level.displayattr[0] is None:
1306 self.beep()
1306 self.beep()
1307 else:
1307 else:
1308 self.report("hideattr")
1308 self.report("hideattr")
1309 level.hiddenattrs.add(level.displayattr[1])
1309 level.hiddenattrs.add(level.displayattr[1])
1310 level.moveto(level.curx, level.cury, refresh=True)
1310 level.moveto(level.curx, level.cury, refresh=True)
1311
1311
1312 def cmd_unhideattrs(self):
1312 def cmd_unhideattrs(self):
1313 """
1313 """
1314 Make all attributes visible again.
1314 Make all attributes visible again.
1315 """
1315 """
1316 level = self.levels[-1]
1316 level = self.levels[-1]
1317 self.report("unhideattrs")
1317 self.report("unhideattrs")
1318 level.hiddenattrs.clear()
1318 level.hiddenattrs.clear()
1319 level.moveto(level.curx, level.cury, refresh=True)
1319 level.moveto(level.curx, level.cury, refresh=True)
1320
1320
1321 def cmd_goto(self):
1321 def cmd_goto(self):
1322 """
1322 """
1323 Jump to a row. The row number can be entered at the
1323 Jump to a row. The row number can be entered at the
1324 bottom of the screen.
1324 bottom of the screen.
1325 """
1325 """
1326 self.startkeyboardinput("goto")
1326 self.startkeyboardinput("goto")
1327
1327
1328 def cmd_find(self):
1328 def cmd_find(self):
1329 """
1329 """
1330 Search forward for a row. The search condition can be entered at the
1330 Search forward for a row. The search condition can be entered at the
1331 bottom of the screen.
1331 bottom of the screen.
1332 """
1332 """
1333 self.startkeyboardinput("find")
1333 self.startkeyboardinput("find")
1334
1334
1335 def cmd_findbackwards(self):
1335 def cmd_findbackwards(self):
1336 """
1336 """
1337 Search backward for a row. The search condition can be entered at the
1337 Search backward for a row. The search condition can be entered at the
1338 bottom of the screen.
1338 bottom of the screen.
1339 """
1339 """
1340 self.startkeyboardinput("findbackwards")
1340 self.startkeyboardinput("findbackwards")
1341
1341
1342 def cmd_help(self):
1342 def cmd_help(self):
1343 """
1343 """
1344 Opens the help screen as a new browser level, describing keyboard
1344 Opens the help screen as a new browser level, describing keyboard
1345 shortcuts.
1345 shortcuts.
1346 """
1346 """
1347 for level in self.levels:
1347 for level in self.levels:
1348 if isinstance(level.input, _BrowserHelp):
1348 if isinstance(level.input, _BrowserHelp):
1349 curses.beep()
1349 curses.beep()
1350 self.report(CommandError("help already active"))
1350 self.report(CommandError("help already active"))
1351 return
1351 return
1352
1352
1353 self.enter(_BrowserHelp(self))
1353 self.enter(_BrowserHelp(self))
1354
1354
1355 def cmd_quit(self):
1355 def cmd_quit(self):
1356 """
1356 """
1357 Quit the browser and return to the IPython prompt.
1357 Quit the browser and return to the IPython prompt.
1358 """
1358 """
1359 self.returnvalue = None
1359 self.returnvalue = None
1360 return True
1360 return True
1361
1361
1362 def sigwinchhandler(self, signal, frame):
1362 def sigwinchhandler(self, signal, frame):
1363 self.resized = True
1363 self.resized = True
1364
1364
1365 def _dodisplay(self, scr):
1365 def _dodisplay(self, scr):
1366 """
1366 """
1367 This method is the workhorse of the browser. It handles screen
1367 This method is the workhorse of the browser. It handles screen
1368 drawing and the keyboard.
1368 drawing and the keyboard.
1369 """
1369 """
1370 self.scr = scr
1370 self.scr = scr
1371 curses.halfdelay(1)
1371 curses.halfdelay(1)
1372 footery = 2
1372 footery = 2
1373
1373
1374 keys = []
1374 keys = []
1375 for cmd in ("quit", "help"):
1375 for cmd in ("quit", "help"):
1376 key = self.keymap.findkey(cmd, None)
1376 key = self.keymap.findkey(cmd, None)
1377 if key is not None:
1377 if key is not None:
1378 keys.append("%s=%s" % (self.keylabel(key), cmd))
1378 keys.append("%s=%s" % (self.keylabel(key), cmd))
1379 helpmsg = " | %s" % " ".join(keys)
1379 helpmsg = " | %s" % " ".join(keys)
1380
1380
1381 scr.clear()
1381 scr.clear()
1382 msg = "Fetching first batch of objects..."
1382 msg = "Fetching first batch of objects..."
1383 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1383 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1384 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1384 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1385 scr.refresh()
1385 scr.refresh()
1386
1386
1387 lastc = -1
1387 lastc = -1
1388
1388
1389 self.levels = []
1389 self.levels = []
1390 # enter the first level
1390 # enter the first level
1391 self.enter(self.input, *self.attrs)
1391 self.enter(self.input, *self.attrs)
1392
1392
1393 self._calcheaderlines(None)
1393 self._calcheaderlines(None)
1394
1394
1395 while True:
1395 while True:
1396 level = self.levels[-1]
1396 level = self.levels[-1]
1397 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1397 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1398 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1398 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1399
1399
1400 # Paint object header
1400 # Paint object header
1401 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1401 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1402 lv = self.levels[i]
1402 lv = self.levels[i]
1403 posx = 0
1403 posx = 0
1404 posy = i-self._firstheaderline
1404 posy = i-self._firstheaderline
1405 endx = self.scrsizex
1405 endx = self.scrsizex
1406 if i: # not the first level
1406 if i: # not the first level
1407 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1407 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1408 if not self.levels[i-1].exhausted:
1408 if not self.levels[i-1].exhausted:
1409 msg += "+"
1409 msg += "+"
1410 msg += ") "
1410 msg += ") "
1411 endx -= len(msg)+1
1411 endx -= len(msg)+1
1412 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1412 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1413 for (style, text) in lv.header:
1413 for (style, text) in lv.header:
1414 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1414 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1415 if posx >= endx:
1415 if posx >= endx:
1416 break
1416 break
1417 if i:
1417 if i:
1418 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1418 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1419 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1419 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1420
1420
1421 if not level.items:
1421 if not level.items:
1422 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1422 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1423 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1423 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1424 scr.clrtobot()
1424 scr.clrtobot()
1425 else:
1425 else:
1426 # Paint column headers
1426 # Paint column headers
1427 scr.move(self._headerlines, 0)
1427 scr.move(self._headerlines, 0)
1428 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1428 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1429 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1429 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1430 begx = level.numbersizex+3
1430 begx = level.numbersizex+3
1431 posx = begx-level.datastartx
1431 posx = begx-level.datastartx
1432 for attr in level.displayattrs:
1432 for attr in level.displayattrs:
1433 attrname = attr.name()
1433 attrname = attr.name()
1434 cwidth = level.colwidths[attr]
1434 cwidth = level.colwidths[attr]
1435 header = attrname.ljust(cwidth)
1435 header = attrname.ljust(cwidth)
1436 if attr is level.displayattr[1]:
1436 if attr is level.displayattr[1]:
1437 style = self.style_colheaderhere
1437 style = self.style_colheaderhere
1438 else:
1438 else:
1439 style = self.style_colheader
1439 style = self.style_colheader
1440 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1440 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1441 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1441 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1442 if posx >= self.scrsizex:
1442 if posx >= self.scrsizex:
1443 break
1443 break
1444 else:
1444 else:
1445 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1445 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1446
1446
1447 # Paint rows
1447 # Paint rows
1448 posy = self._headerlines+1+level.datastarty
1448 posy = self._headerlines+1+level.datastarty
1449 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1449 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1450 cache = level.items[i]
1450 cache = level.items[i]
1451 if i == level.cury:
1451 if i == level.cury:
1452 style = self.style_numberhere
1452 style = self.style_numberhere
1453 else:
1453 else:
1454 style = self.style_number
1454 style = self.style_number
1455
1455
1456 posy = self._headerlines+1+i-level.datastarty
1456 posy = self._headerlines+1+i-level.datastarty
1457 posx = begx-level.datastartx
1457 posx = begx-level.datastartx
1458
1458
1459 scr.move(posy, 0)
1459 scr.move(posy, 0)
1460 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1460 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1461 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1461 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1462
1462
1463 for attrname in level.displayattrs:
1463 for attrname in level.displayattrs:
1464 cwidth = level.colwidths[attrname]
1464 cwidth = level.colwidths[attrname]
1465 try:
1465 try:
1466 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1466 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1467 except KeyError:
1467 except KeyError:
1468 align = 2
1468 align = 2
1469 style = astyle.style_nodata
1469 style = astyle.style_nodata
1470 if i == level.cury:
1470 if i == level.cury:
1471 style = self.getstylehere(style)
1471 style = self.getstylehere(style)
1472 padstyle = self.style_datapad
1472 padstyle = self.style_datapad
1473 sepstyle = self.style_sep
1473 sepstyle = self.style_sep
1474 if i == level.cury:
1474 if i == level.cury:
1475 padstyle = self.getstylehere(padstyle)
1475 padstyle = self.getstylehere(padstyle)
1476 sepstyle = self.getstylehere(sepstyle)
1476 sepstyle = self.getstylehere(sepstyle)
1477 if align == 2:
1477 if align == 2:
1478 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1478 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1479 else:
1479 else:
1480 if align == 1:
1480 if align == 1:
1481 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1481 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1482 elif align == 0:
1482 elif align == 0:
1483 pad1 = (cwidth-length)//2
1483 pad1 = (cwidth-length)//2
1484 pad2 = cwidth-length-len(pad1)
1484 pad2 = cwidth-length-len(pad1)
1485 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1485 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1486 for (style, text) in parts:
1486 for (style, text) in parts:
1487 if i == level.cury:
1487 if i == level.cury:
1488 style = self.getstylehere(style)
1488 style = self.getstylehere(style)
1489 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1489 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1490 if posx >= self.scrsizex:
1490 if posx >= self.scrsizex:
1491 break
1491 break
1492 if align == -1:
1492 if align == -1:
1493 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1493 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1494 elif align == 0:
1494 elif align == 0:
1495 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1495 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1496 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1496 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1497 else:
1497 else:
1498 scr.clrtoeol()
1498 scr.clrtoeol()
1499
1499
1500 # Add blank row headers for the rest of the screen
1500 # Add blank row headers for the rest of the screen
1501 for posy in xrange(posy+1, self.scrsizey-2):
1501 for posy in xrange(posy+1, self.scrsizey-2):
1502 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1502 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1503 scr.clrtoeol()
1503 scr.clrtoeol()
1504
1504
1505 posy = self.scrsizey-footery
1505 posy = self.scrsizey-footery
1506 # Display footer
1506 # Display footer
1507 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1507 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1508
1508
1509 if level.exhausted:
1509 if level.exhausted:
1510 flag = ""
1510 flag = ""
1511 else:
1511 else:
1512 flag = "+"
1512 flag = "+"
1513
1513
1514 endx = self.scrsizex-len(helpmsg)-1
1514 endx = self.scrsizex-len(helpmsg)-1
1515 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1515 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1516
1516
1517 posx = 0
1517 posx = 0
1518 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1518 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1519 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1519 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1520 try:
1520 try:
1521 item = level.items[level.cury].item
1521 item = level.items[level.cury].item
1522 except IndexError: # empty
1522 except IndexError: # empty
1523 pass
1523 pass
1524 else:
1524 else:
1525 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1525 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1526 if not isinstance(nostyle, int):
1526 if not isinstance(nostyle, int):
1527 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1527 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1528 if posx >= endx:
1528 if posx >= endx:
1529 break
1529 break
1530
1530
1531 attrstyle = [(astyle.style_default, "no attribute")]
1531 attrstyle = [(astyle.style_default, "no attribute")]
1532 attr = level.displayattr[1]
1532 attr = level.displayattr[1]
1533 if attr is not ipipe.noitem and not isinstance(attr, ipipe.SelfDescriptor):
1533 if attr is not ipipe.noitem and not isinstance(attr, ipipe.SelfDescriptor):
1534 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1534 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1535 posx += self.addstr(posy, posx, 0, endx, attr.name(), self.style_footer)
1535 posx += self.addstr(posy, posx, 0, endx, attr.name(), self.style_footer)
1536 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1536 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1537 try:
1537 try:
1538 value = attr.value(item)
1538 value = attr.value(item)
1539 except (SystemExit, KeyboardInterrupt):
1539 except (SystemExit, KeyboardInterrupt):
1540 raise
1540 raise
1541 except Exception, exc:
1541 except Exception, exc:
1542 value = exc
1542 value = exc
1543 if value is not ipipe.noitem:
1543 if value is not ipipe.noitem:
1544 attrstyle = ipipe.xrepr(value, "footer")
1544 attrstyle = ipipe.xrepr(value, "footer")
1545 for (nostyle, text) in attrstyle:
1545 for (nostyle, text) in attrstyle:
1546 if not isinstance(nostyle, int):
1546 if not isinstance(nostyle, int):
1547 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1547 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1548 if posx >= endx:
1548 if posx >= endx:
1549 break
1549 break
1550
1550
1551 try:
1551 try:
1552 # Display input prompt
1552 # Display input prompt
1553 if self.mode in self.prompts:
1553 if self.mode in self.prompts:
1554 history = self.prompts[self.mode]
1554 history = self.prompts[self.mode]
1555 posx = 0
1555 posx = 0
1556 posy = self.scrsizey-1
1556 posy = self.scrsizey-1
1557 posx += self.addstr(posy, posx, 0, endx, history.prompt, astyle.style_default)
1557 posx += self.addstr(posy, posx, 0, endx, history.prompt, astyle.style_default)
1558 posx += self.addstr(posy, posx, 0, endx, " [", astyle.style_default)
1558 posx += self.addstr(posy, posx, 0, endx, " [", astyle.style_default)
1559 if history.cury==-1:
1559 if history.cury==-1:
1560 text = "new"
1560 text = "new"
1561 else:
1561 else:
1562 text = str(history.cury+1)
1562 text = str(history.cury+1)
1563 posx += self.addstr(posy, posx, 0, endx, text, astyle.style_type_number)
1563 posx += self.addstr(posy, posx, 0, endx, text, astyle.style_type_number)
1564 if history.history:
1564 if history.history:
1565 posx += self.addstr(posy, posx, 0, endx, "/", astyle.style_default)
1565 posx += self.addstr(posy, posx, 0, endx, "/", astyle.style_default)
1566 posx += self.addstr(posy, posx, 0, endx, str(len(history.history)), astyle.style_type_number)
1566 posx += self.addstr(posy, posx, 0, endx, str(len(history.history)), astyle.style_type_number)
1567 posx += self.addstr(posy, posx, 0, endx, "]: ", astyle.style_default)
1567 posx += self.addstr(posy, posx, 0, endx, "]: ", astyle.style_default)
1568 inputstartx = posx
1568 inputstartx = posx
1569 posx += self.addstr(posy, posx, 0, endx, history.input, astyle.style_default)
1569 posx += self.addstr(posy, posx, 0, endx, history.input, astyle.style_default)
1570 # Display report
1570 # Display report
1571 else:
1571 else:
1572 if self._report is not None:
1572 if self._report is not None:
1573 if isinstance(self._report, Exception):
1573 if isinstance(self._report, Exception):
1574 style = self.getstyle(astyle.style_error)
1574 style = self.getstyle(astyle.style_error)
1575 if self._report.__class__.__module__ == "exceptions":
1575 if self._report.__class__.__module__ == "exceptions":
1576 msg = "%s: %s" % \
1576 msg = "%s: %s" % \
1577 (self._report.__class__.__name__, self._report)
1577 (self._report.__class__.__name__, self._report)
1578 else:
1578 else:
1579 msg = "%s.%s: %s" % \
1579 msg = "%s.%s: %s" % \
1580 (self._report.__class__.__module__,
1580 (self._report.__class__.__module__,
1581 self._report.__class__.__name__, self._report)
1581 self._report.__class__.__name__, self._report)
1582 else:
1582 else:
1583 style = self.getstyle(self.style_report)
1583 style = self.getstyle(self.style_report)
1584 msg = self._report
1584 msg = self._report
1585 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1585 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1586 self._report = None
1586 self._report = None
1587 else:
1587 else:
1588 scr.move(self.scrsizey-1, 0)
1588 scr.move(self.scrsizey-1, 0)
1589 except curses.error:
1589 except curses.error:
1590 # Protect against errors from writing to the last line
1590 # Protect against errors from writing to the last line
1591 pass
1591 pass
1592 scr.clrtoeol()
1592 scr.clrtoeol()
1593
1593
1594 # Position cursor
1594 # Position cursor
1595 if self.mode in self.prompts:
1595 if self.mode in self.prompts:
1596 history = self.prompts[self.mode]
1596 history = self.prompts[self.mode]
1597 scr.move(self.scrsizey-1, inputstartx+history.curx)
1597 scr.move(self.scrsizey-1, inputstartx+history.curx)
1598 else:
1598 else:
1599 scr.move(
1599 scr.move(
1600 1+self._headerlines+level.cury-level.datastarty,
1600 1+self._headerlines+level.cury-level.datastarty,
1601 level.numbersizex+3+level.curx-level.datastartx
1601 level.numbersizex+3+level.curx-level.datastartx
1602 )
1602 )
1603 scr.refresh()
1603 scr.refresh()
1604
1604
1605 # Check keyboard
1605 # Check keyboard
1606 while True:
1606 while True:
1607 c = scr.getch()
1607 c = scr.getch()
1608 if self.resized:
1608 if self.resized:
1609 size = fcntl.ioctl(0, tty.TIOCGWINSZ, "12345678")
1609 size = fcntl.ioctl(0, tty.TIOCGWINSZ, "12345678")
1610 size = struct.unpack("4H", size)
1610 size = struct.unpack("4H", size)
1611 oldsize = scr.getmaxyx()
1611 oldsize = scr.getmaxyx()
1612 scr.erase()
1612 scr.erase()
1613 curses.resize_term(size[0], size[1])
1613 curses.resize_term(size[0], size[1])
1614 newsize = scr.getmaxyx()
1614 newsize = scr.getmaxyx()
1615 scr.erase()
1615 scr.erase()
1616 for l in self.levels:
1616 for l in self.levels:
1617 l.mainsizey += newsize[0]-oldsize[0]
1617 l.mainsizey += newsize[0]-oldsize[0]
1618 l.moveto(l.curx, l.cury, refresh=True)
1618 l.moveto(l.curx, l.cury, refresh=True)
1619 scr.refresh()
1619 scr.refresh()
1620 self.resized = False
1620 self.resized = False
1621 break # Redisplay
1621 break # Redisplay
1622 if self.mode in self.prompts:
1622 if self.mode in self.prompts:
1623 if self.prompts[self.mode].handlekey(self, c):
1623 if self.prompts[self.mode].handlekey(self, c):
1624 break # Redisplay
1624 break # Redisplay
1625 else:
1625 else:
1626 # if no key is pressed slow down and beep again
1626 # if no key is pressed slow down and beep again
1627 if c == -1:
1627 if c == -1:
1628 self.stepx = 1.
1628 self.stepx = 1.
1629 self.stepy = 1.
1629 self.stepy = 1.
1630 self._dobeep = True
1630 self._dobeep = True
1631 else:
1631 else:
1632 # if a different key was pressed slow down and beep too
1632 # if a different key was pressed slow down and beep too
1633 if c != lastc:
1633 if c != lastc:
1634 lastc = c
1634 lastc = c
1635 self.stepx = 1.
1635 self.stepx = 1.
1636 self.stepy = 1.
1636 self.stepy = 1.
1637 self._dobeep = True
1637 self._dobeep = True
1638 cmdname = self.keymap.get(c, None)
1638 cmdname = self.keymap.get(c, None)
1639 if cmdname is None:
1639 if cmdname is None:
1640 self.report(
1640 self.report(
1641 UnassignedKeyError("Unassigned key %s" %
1641 UnassignedKeyError("Unassigned key %s" %
1642 self.keylabel(c)))
1642 self.keylabel(c)))
1643 else:
1643 else:
1644 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1644 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1645 if cmdfunc is None:
1645 if cmdfunc is None:
1646 self.report(
1646 self.report(
1647 UnknownCommandError("Unknown command %r" %
1647 UnknownCommandError("Unknown command %r" %
1648 (cmdname,)))
1648 (cmdname,)))
1649 elif cmdfunc():
1649 elif cmdfunc():
1650 returnvalue = self.returnvalue
1650 returnvalue = self.returnvalue
1651 self.returnvalue = None
1651 self.returnvalue = None
1652 return returnvalue
1652 return returnvalue
1653 self.stepx = self.nextstepx(self.stepx)
1653 self.stepx = self.nextstepx(self.stepx)
1654 self.stepy = self.nextstepy(self.stepy)
1654 self.stepy = self.nextstepy(self.stepy)
1655 curses.flushinp() # get rid of type ahead
1655 curses.flushinp() # get rid of type ahead
1656 break # Redisplay
1656 break # Redisplay
1657 self.scr = None
1657 self.scr = None
1658
1658
1659 def display(self):
1659 def display(self):
1660 if hasattr(curses, "resize_term"):
1660 if hasattr(curses, "resize_term"):
1661 oldhandler = signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1661 oldhandler = signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1662 try:
1662 try:
1663 return curses.wrapper(self._dodisplay)
1663 return curses.wrapper(self._dodisplay)
1664 finally:
1664 finally:
1665 signal.signal(signal.SIGWINCH, oldhandler)
1665 signal.signal(signal.SIGWINCH, oldhandler)
1666 else:
1666 else:
1667 return curses.wrapper(self._dodisplay)
1667 return curses.wrapper(self._dodisplay)
@@ -1,2121 +1,2121 b''
1 # -*- coding: iso-8859-1 -*-
1 # -*- coding: iso-8859-1 -*-
2
2
3 """
3 """
4 ``ipipe`` provides classes to be used in an interactive Python session. Doing a
4 ``ipipe`` provides classes to be used in an interactive Python session. Doing a
5 ``from ipipe import *`` is the preferred way to do this. The name of all
5 ``from ipipe import *`` is the preferred way to do this. The name of all
6 objects imported this way starts with ``i`` to minimize collisions.
6 objects imported this way starts with ``i`` to minimize collisions.
7
7
8 ``ipipe`` supports "pipeline expressions", which is something resembling Unix
8 ``ipipe`` supports "pipeline expressions", which is something resembling Unix
9 pipes. An example is:
9 pipes. An example is:
10
10
11 >>> ienv | isort("key.lower()")
11 >>> ienv | isort("key.lower()")
12
12
13 This gives a listing of all environment variables sorted by name.
13 This gives a listing of all environment variables sorted by name.
14
14
15
15
16 There are three types of objects in a pipeline expression:
16 There are three types of objects in a pipeline expression:
17
17
18 * ``Table``s: These objects produce items. Examples are ``ils`` (listing the
18 * ``Table``s: These objects produce items. Examples are ``ils`` (listing the
19 current directory, ``ienv`` (listing environment variables), ``ipwd`` (listing
19 current directory, ``ienv`` (listing environment variables), ``ipwd`` (listing
20 user accounts) and ``igrp`` (listing user groups). A ``Table`` must be the
20 user accounts) 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 ``ibrowse`` which is a ``curses``
32 display objects will be used. One example is ``ibrowse`` which is a ``curses``
33 based browser.
33 based browser.
34
34
35
35
36 Adding support for pipeline expressions to your own objects can be done through
36 Adding support for pipeline expressions to your own objects can be done through
37 three extensions points (all of them optional):
37 three extensions points (all of them optional):
38
38
39 * An object that will be displayed as a row by a ``Display`` object should
39 * An object that will be displayed as a row by a ``Display`` object should
40 implement the method ``__xattrs__(self, mode)`` method or register an
40 implement the method ``__xattrs__(self, mode)`` method or register an
41 implementation of the generic function ``xattrs``. For more info see ``xattrs``.
41 implementation of the generic function ``xattrs``. For more info see ``xattrs``.
42
42
43 * When an object ``foo`` is displayed by a ``Display`` object, the generic
43 * When an object ``foo`` is displayed by a ``Display`` object, the generic
44 function ``xrepr`` is used.
44 function ``xrepr`` is used.
45
45
46 * Objects that can be iterated by ``Pipe``s must iterable. For special cases,
46 * Objects that can be iterated by ``Pipe``s must iterable. For special cases,
47 where iteration for display is different than the normal iteration a special
47 where iteration for display is different than the normal iteration a special
48 implementation can be registered with the generic function ``xiter``. This makes
48 implementation can be registered with the generic function ``xiter``. This makes
49 it possible to use dictionaries and modules in pipeline expressions, for example:
49 it possible to use dictionaries and modules in pipeline expressions, for example:
50
50
51 >>> import sys
51 >>> import sys
52 >>> sys | ifilter("isinstance(value, int)") | idump
52 >>> sys | ifilter("isinstance(value, int)") | idump
53 key |value
53 key |value
54 api_version| 1012
54 api_version| 1012
55 dllhandle | 503316480
55 dllhandle | 503316480
56 hexversion | 33817328
56 hexversion | 33817328
57 maxint |2147483647
57 maxint |2147483647
58 maxunicode | 65535
58 maxunicode | 65535
59 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
59 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
60 ...
60 ...
61
61
62 Note: The expression strings passed to ``ifilter()`` and ``isort()`` can
62 Note: The expression strings passed to ``ifilter()`` and ``isort()`` can
63 refer to the object to be filtered or sorted via the variable ``_`` and to any
63 refer to the object to be filtered or sorted via the variable ``_`` and to any
64 of the attributes of the object, i.e.:
64 of the attributes of the object, i.e.:
65
65
66 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
66 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()")
67
67
68 does the same as
68 does the same as
69
69
70 >>> sys.modules | ifilter("value is not None") | isort("key.lower()")
70 >>> sys.modules | ifilter("value is not None") | isort("key.lower()")
71
71
72 In addition to expression strings, it's possible to pass callables (taking
72 In addition to expression strings, it's possible to pass callables (taking
73 the object as an argument) to ``ifilter()``, ``isort()`` and ``ieval()``:
73 the object as an argument) to ``ifilter()``, ``isort()`` and ``ieval()``:
74
74
75 >>> sys | ifilter(lambda _:isinstance(_.value, int)) \
75 >>> sys | ifilter(lambda _:isinstance(_.value, int)) \
76 ... | ieval(lambda _: (_.key, hex(_.value))) | idump
76 ... | ieval(lambda _: (_.key, hex(_.value))) | idump
77 0 |1
77 0 |1
78 api_version|0x3f4
78 api_version|0x3f4
79 dllhandle |0x1e000000
79 dllhandle |0x1e000000
80 hexversion |0x20402f0
80 hexversion |0x20402f0
81 maxint |0x7fffffff
81 maxint |0x7fffffff
82 maxunicode |0xffff
82 maxunicode |0xffff
83 """
83 """
84
84
85 import sys, os, os.path, stat, glob, new, csv, datetime, types
85 import sys, os, os.path, stat, glob, new, csv, datetime, types
86 import itertools, mimetypes
86 import itertools, mimetypes
87
87
88 try: # Python 2.3 compatibility
88 try: # Python 2.3 compatibility
89 import collections
89 import collections
90 except ImportError:
90 except ImportError:
91 deque = list
91 deque = list
92 else:
92 else:
93 deque = collections.deque
93 deque = collections.deque
94
94
95 try: # Python 2.3 compatibility
95 try: # Python 2.3 compatibility
96 set
96 set
97 except NameError:
97 except NameError:
98 import sets
98 import sets
99 set = sets.Set
99 set = sets.Set
100
100
101 try: # Python 2.3 compatibility
101 try: # Python 2.3 compatibility
102 sorted
102 sorted
103 except NameError:
103 except NameError:
104 def sorted(iterator, key=None, reverse=False):
104 def sorted(iterator, key=None, reverse=False):
105 items = list(iterator)
105 items = list(iterator)
106 if key is not None:
106 if key is not None:
107 items.sort(lambda i1, i2: cmp(key(i1), key(i2)))
107 items.sort(lambda i1, i2: cmp(key(i1), key(i2)))
108 else:
108 else:
109 items.sort()
109 items.sort()
110 if reverse:
110 if reverse:
111 items.reverse()
111 items.reverse()
112 return items
112 return items
113
113
114 try:
114 try:
115 import pwd
115 import pwd
116 except ImportError:
116 except ImportError:
117 pwd = None
117 pwd = None
118
118
119 try:
119 try:
120 import grp
120 import grp
121 except ImportError:
121 except ImportError:
122 grp = None
122 grp = None
123
123
124 from IPython.external import simplegeneric
124 from IPython.external import simplegeneric
125
125
126 import path
126 import path
127 try:
127 try:
128 from IPython import genutils, ipapi
128 from IPython import genutils, ipapi
129 except ImportError:
129 except ImportError:
130 genutils = None
130 genutils = None
131 ipapi = None
131 ipapi = None
132
132
133 import astyle
133 import astyle
134
134
135
135
136 __all__ = [
136 __all__ = [
137 "ifile", "ils", "iglob", "iwalk", "ipwdentry", "ipwd", "igrpentry", "igrp",
137 "ifile", "ils", "iglob", "iwalk", "ipwdentry", "ipwd", "igrpentry", "igrp",
138 "icsv", "ix", "ichain", "isort", "ifilter", "ieval", "ienum", "ienv",
138 "icsv", "ix", "ichain", "isort", "ifilter", "ieval", "ienum", "ienv",
139 "idump", "iless"
139 "idump", "iless"
140 ]
140 ]
141
141
142
142
143 os.stat_float_times(True) # enable microseconds
143 os.stat_float_times(True) # enable microseconds
144
144
145
145
146 class AttrNamespace(object):
146 class AttrNamespace(object):
147 """
147 """
148 Helper class that is used for providing a namespace for evaluating
148 Helper class that is used for providing a namespace for evaluating
149 expressions containing attribute names of an object.
149 expressions containing attribute names of an object.
150 """
150 """
151 def __init__(self, wrapped):
151 def __init__(self, wrapped):
152 self.wrapped = wrapped
152 self.wrapped = wrapped
153
153
154 def __getitem__(self, name):
154 def __getitem__(self, name):
155 if name == "_":
155 if name == "_":
156 return self.wrapped
156 return self.wrapped
157 try:
157 try:
158 return getattr(self.wrapped, name)
158 return getattr(self.wrapped, name)
159 except AttributeError:
159 except AttributeError:
160 raise KeyError(name)
160 raise KeyError(name)
161
161
162 # Python 2.3 compatibility
162 # Python 2.3 compatibility
163 # use eval workaround to find out which names are used in the
163 # use eval workaround to find out which names are used in the
164 # eval string and put them into the locals. This works for most
164 # eval string and put them into the locals. This works for most
165 # normal uses case, bizarre ones like accessing the locals()
165 # normal uses case, bizarre ones like accessing the locals()
166 # will fail
166 # will fail
167 try:
167 try:
168 eval("_", None, AttrNamespace(None))
168 eval("_", None, AttrNamespace(None))
169 except TypeError:
169 except TypeError:
170 real_eval = eval
170 real_eval = eval
171 def eval(codestring, _globals, _locals):
171 def eval(codestring, _globals, _locals):
172 """
172 """
173 eval(source[, globals[, locals]]) -> value
173 eval(source[, globals[, locals]]) -> value
174
174
175 Evaluate the source in the context of globals and locals.
175 Evaluate the source in the context of globals and locals.
176 The source may be a string representing a Python expression
176 The source may be a string representing a Python expression
177 or a code object as returned by compile().
177 or a code object as returned by compile().
178 The globals must be a dictionary and locals can be any mappping.
178 The globals must be a dictionary and locals can be any mappping.
179
179
180 This function is a workaround for the shortcomings of
180 This function is a workaround for the shortcomings of
181 Python 2.3's eval.
181 Python 2.3's eval.
182 """
182 """
183
183
184 if isinstance(codestring, basestring):
184 if isinstance(codestring, basestring):
185 code = compile(codestring, "_eval", "eval")
185 code = compile(codestring, "_eval", "eval")
186 else:
186 else:
187 code = codestring
187 code = codestring
188 newlocals = {}
188 newlocals = {}
189 for name in code.co_names:
189 for name in code.co_names:
190 try:
190 try:
191 newlocals[name] = _locals[name]
191 newlocals[name] = _locals[name]
192 except KeyError:
192 except KeyError:
193 pass
193 pass
194 return real_eval(code, _globals, newlocals)
194 return real_eval(code, _globals, newlocals)
195
195
196
196
197 noitem = object()
197 noitem = object()
198
198
199 def item(iterator, index, default=noitem):
199 def item(iterator, index, default=noitem):
200 """
200 """
201 Return the ``index``th item from the iterator ``iterator``.
201 Return the ``index``th item from the iterator ``iterator``.
202 ``index`` must be an integer (negative integers are relative to the
202 ``index`` must be an integer (negative integers are relative to the
203 end (i.e. the last items produced by the iterator)).
203 end (i.e. the last items produced by the iterator)).
204
204
205 If ``default`` is given, this will be the default value when
205 If ``default`` is given, this will be the default value when
206 the iterator doesn't contain an item at this position. Otherwise an
206 the iterator doesn't contain an item at this position. Otherwise an
207 ``IndexError`` will be raised.
207 ``IndexError`` will be raised.
208
208
209 Note that using this function will partially or totally exhaust the
209 Note that using this function will partially or totally exhaust the
210 iterator.
210 iterator.
211 """
211 """
212 i = index
212 i = index
213 if i>=0:
213 if i>=0:
214 for item in iterator:
214 for item in iterator:
215 if not i:
215 if not i:
216 return item
216 return item
217 i -= 1
217 i -= 1
218 else:
218 else:
219 i = -index
219 i = -index
220 cache = deque()
220 cache = deque()
221 for item in iterator:
221 for item in iterator:
222 cache.append(item)
222 cache.append(item)
223 if len(cache)>i:
223 if len(cache)>i:
224 cache.popleft()
224 cache.popleft()
225 if len(cache)==i:
225 if len(cache)==i:
226 return cache.popleft()
226 return cache.popleft()
227 if default is noitem:
227 if default is noitem:
228 raise IndexError(index)
228 raise IndexError(index)
229 else:
229 else:
230 return default
230 return default
231
231
232
232
233 def getglobals(g):
233 def getglobals(g):
234 """
234 """
235 Return the global namespace that is used for expression strings in
235 Return the global namespace that is used for expression strings in
236 ``ifilter`` and others. This is ``g`` or (if ``g`` is ``None``) IPython's
236 ``ifilter`` and others. This is ``g`` or (if ``g`` is ``None``) IPython's
237 user namespace.
237 user namespace.
238 """
238 """
239 if g is None:
239 if g is None:
240 if ipapi is not None:
240 if ipapi is not None:
241 api = ipapi.get()
241 api = ipapi.get()
242 if api is not None:
242 if api is not None:
243 return api.user_ns
243 return api.user_ns
244 return globals()
244 return globals()
245 return g
245 return g
246
246
247
247
248 class Descriptor(object):
248 class Descriptor(object):
249 """
249 """
250 A ``Descriptor`` object is used for describing the attributes of objects.
250 A ``Descriptor`` object is used for describing the attributes of objects.
251 """
251 """
252 def __hash__(self):
252 def __hash__(self):
253 return hash(self.__class__) ^ hash(self.key())
253 return hash(self.__class__) ^ hash(self.key())
254
254
255 def __eq__(self, other):
255 def __eq__(self, other):
256 return self.__class__ is other.__class__ and self.key() == other.key()
256 return self.__class__ is other.__class__ and self.key() == other.key()
257
257
258 def __ne__(self, other):
258 def __ne__(self, other):
259 return self.__class__ is not other.__class__ or self.key() != other.key()
259 return self.__class__ is not other.__class__ or self.key() != other.key()
260
260
261 def key(self):
261 def key(self):
262 pass
262 pass
263
263
264 def name(self):
264 def name(self):
265 """
265 """
266 Return the name of this attribute for display by a ``Display`` object
266 Return the name of this attribute for display by a ``Display`` object
267 (e.g. as a column title).
267 (e.g. as a column title).
268 """
268 """
269 key = self.key()
269 key = self.key()
270 if key is None:
270 if key is None:
271 return "_"
271 return "_"
272 return str(key)
272 return str(key)
273
273
274 def attrtype(self, obj):
274 def attrtype(self, obj):
275 """
275 """
276 Return the type of this attribute (i.e. something like "attribute" or
276 Return the type of this attribute (i.e. something like "attribute" or
277 "method").
277 "method").
278 """
278 """
279
279
280 def valuetype(self, obj):
280 def valuetype(self, obj):
281 """
281 """
282 Return the type of this attribute value of the object ``obj``.
282 Return the type of this attribute value of the object ``obj``.
283 """
283 """
284
284
285 def value(self, obj):
285 def value(self, obj):
286 """
286 """
287 Return the value of this attribute of the object ``obj``.
287 Return the value of this attribute of the object ``obj``.
288 """
288 """
289
289
290 def doc(self, obj):
290 def doc(self, obj):
291 """
291 """
292 Return the documentation for this attribute.
292 Return the documentation for this attribute.
293 """
293 """
294
294
295 def shortdoc(self, obj):
295 def shortdoc(self, obj):
296 """
296 """
297 Return a short documentation for this attribute (defaulting to the
297 Return a short documentation for this attribute (defaulting to the
298 first line).
298 first line).
299 """
299 """
300 doc = self.doc(obj)
300 doc = self.doc(obj)
301 if doc is not None:
301 if doc is not None:
302 doc = doc.strip().splitlines()[0].strip()
302 doc = doc.strip().splitlines()[0].strip()
303 return doc
303 return doc
304
304
305 def iter(self, obj):
305 def iter(self, obj):
306 """
306 """
307 Return an iterator for this attribute of the object ``obj``.
307 Return an iterator for this attribute of the object ``obj``.
308 """
308 """
309 return xiter(self.value(obj))
309 return xiter(self.value(obj))
310
310
311
311
312 class SelfDescriptor(Descriptor):
312 class SelfDescriptor(Descriptor):
313 """
313 """
314 A ``SelfDescriptor`` describes the object itself.
314 A ``SelfDescriptor`` describes the object itself.
315 """
315 """
316 def key(self):
316 def key(self):
317 return None
317 return None
318
318
319 def attrtype(self, obj):
319 def attrtype(self, obj):
320 return "self"
320 return "self"
321
321
322 def valuetype(self, obj):
322 def valuetype(self, obj):
323 return type(obj)
323 return type(obj)
324
324
325 def value(self, obj):
325 def value(self, obj):
326 return obj
326 return obj
327
327
328 def __repr__(self):
328 def __repr__(self):
329 return "Self"
329 return "Self"
330
330
331 selfdescriptor = SelfDescriptor() # there's no need for more than one
331 selfdescriptor = SelfDescriptor() # there's no need for more than one
332
332
333
333
334 class AttributeDescriptor(Descriptor):
334 class AttributeDescriptor(Descriptor):
335 """
335 """
336 An ``AttributeDescriptor`` describes a simple attribute of an object.
336 An ``AttributeDescriptor`` describes a simple attribute of an object.
337 """
337 """
338 __slots__ = ("_name", "_doc")
338 __slots__ = ("_name", "_doc")
339
339
340 def __init__(self, name, doc=None):
340 def __init__(self, name, doc=None):
341 self._name = name
341 self._name = name
342 self._doc = doc
342 self._doc = doc
343
343
344 def key(self):
344 def key(self):
345 return self._name
345 return self._name
346
346
347 def doc(self, obj):
347 def doc(self, obj):
348 return self._doc
348 return self._doc
349
349
350 def attrtype(self, obj):
350 def attrtype(self, obj):
351 return "attr"
351 return "attr"
352
352
353 def valuetype(self, obj):
353 def valuetype(self, obj):
354 return type(getattr(obj, self._name))
354 return type(getattr(obj, self._name))
355
355
356 def value(self, obj):
356 def value(self, obj):
357 return getattr(obj, self._name)
357 return getattr(obj, self._name)
358
358
359 def __repr__(self):
359 def __repr__(self):
360 if self._doc is None:
360 if self._doc is None:
361 return "Attribute(%r)" % self._name
361 return "Attribute(%r)" % self._name
362 else:
362 else:
363 return "Attribute(%r, %r)" % (self._name, self._doc)
363 return "Attribute(%r, %r)" % (self._name, self._doc)
364
364
365
365
366 class IndexDescriptor(Descriptor):
366 class IndexDescriptor(Descriptor):
367 """
367 """
368 An ``IndexDescriptor`` describes an "attribute" of an object that is fetched
368 An ``IndexDescriptor`` describes an "attribute" of an object that is fetched
369 via ``__getitem__``.
369 via ``__getitem__``.
370 """
370 """
371 __slots__ = ("_index",)
371 __slots__ = ("_index",)
372
372
373 def __init__(self, index):
373 def __init__(self, index):
374 self._index = index
374 self._index = index
375
375
376 def key(self):
376 def key(self):
377 return self._index
377 return self._index
378
378
379 def attrtype(self, obj):
379 def attrtype(self, obj):
380 return "item"
380 return "item"
381
381
382 def valuetype(self, obj):
382 def valuetype(self, obj):
383 return type(obj[self._index])
383 return type(obj[self._index])
384
384
385 def value(self, obj):
385 def value(self, obj):
386 return obj[self._index]
386 return obj[self._index]
387
387
388 def __repr__(self):
388 def __repr__(self):
389 return "Index(%r)" % self._index
389 return "Index(%r)" % self._index
390
390
391
391
392 class MethodDescriptor(Descriptor):
392 class MethodDescriptor(Descriptor):
393 """
393 """
394 A ``MethodDescriptor`` describes a method of an object that can be called
394 A ``MethodDescriptor`` describes a method of an object that can be called
395 without argument. Note that this method shouldn't change the object.
395 without argument. Note that this method shouldn't change the object.
396 """
396 """
397 __slots__ = ("_name", "_doc")
397 __slots__ = ("_name", "_doc")
398
398
399 def __init__(self, name, doc=None):
399 def __init__(self, name, doc=None):
400 self._name = name
400 self._name = name
401 self._doc = doc
401 self._doc = doc
402
402
403 def key(self):
403 def key(self):
404 return self._name
404 return self._name
405
405
406 def doc(self, obj):
406 def doc(self, obj):
407 if self._doc is None:
407 if self._doc is None:
408 return getattr(obj, self._name).__doc__
408 return getattr(obj, self._name).__doc__
409 return self._doc
409 return self._doc
410
410
411 def attrtype(self, obj):
411 def attrtype(self, obj):
412 return "method"
412 return "method"
413
413
414 def valuetype(self, obj):
414 def valuetype(self, obj):
415 return type(self.value(obj))
415 return type(self.value(obj))
416
416
417 def value(self, obj):
417 def value(self, obj):
418 return getattr(obj, self._name)()
418 return getattr(obj, self._name)()
419
419
420 def __repr__(self):
420 def __repr__(self):
421 if self._doc is None:
421 if self._doc is None:
422 return "Method(%r)" % self._name
422 return "Method(%r)" % self._name
423 else:
423 else:
424 return "Method(%r, %r)" % (self._name, self._doc)
424 return "Method(%r, %r)" % (self._name, self._doc)
425
425
426
426
427 class IterAttributeDescriptor(Descriptor):
427 class IterAttributeDescriptor(Descriptor):
428 """
428 """
429 An ``IterAttributeDescriptor`` works like an ``AttributeDescriptor`` but
429 An ``IterAttributeDescriptor`` works like an ``AttributeDescriptor`` but
430 doesn't return an attribute values (because this value might be e.g. a large
430 doesn't return an attribute values (because this value might be e.g. a large
431 list).
431 list).
432 """
432 """
433 __slots__ = ("_name", "_doc")
433 __slots__ = ("_name", "_doc")
434
434
435 def __init__(self, name, doc=None):
435 def __init__(self, name, doc=None):
436 self._name = name
436 self._name = name
437 self._doc = doc
437 self._doc = doc
438
438
439 def key(self):
439 def key(self):
440 return self._name
440 return self._name
441
441
442 def doc(self, obj):
442 def doc(self, obj):
443 return self._doc
443 return self._doc
444
444
445 def attrtype(self, obj):
445 def attrtype(self, obj):
446 return "iter"
446 return "iter"
447
447
448 def valuetype(self, obj):
448 def valuetype(self, obj):
449 return noitem
449 return noitem
450
450
451 def value(self, obj):
451 def value(self, obj):
452 return noitem
452 return noitem
453
453
454 def iter(self, obj):
454 def iter(self, obj):
455 return xiter(getattr(obj, self._name))
455 return xiter(getattr(obj, self._name))
456
456
457 def __repr__(self):
457 def __repr__(self):
458 if self._doc is None:
458 if self._doc is None:
459 return "IterAttribute(%r)" % self._name
459 return "IterAttribute(%r)" % self._name
460 else:
460 else:
461 return "IterAttribute(%r, %r)" % (self._name, self._doc)
461 return "IterAttribute(%r, %r)" % (self._name, self._doc)
462
462
463
463
464 class IterMethodDescriptor(Descriptor):
464 class IterMethodDescriptor(Descriptor):
465 """
465 """
466 An ``IterMethodDescriptor`` works like an ``MethodDescriptor`` but doesn't
466 An ``IterMethodDescriptor`` works like an ``MethodDescriptor`` but doesn't
467 return an attribute values (because this value might be e.g. a large list).
467 return an attribute values (because this value might be e.g. a large list).
468 """
468 """
469 __slots__ = ("_name", "_doc")
469 __slots__ = ("_name", "_doc")
470
470
471 def __init__(self, name, doc=None):
471 def __init__(self, name, doc=None):
472 self._name = name
472 self._name = name
473 self._doc = doc
473 self._doc = doc
474
474
475 def key(self):
475 def key(self):
476 return self._name
476 return self._name
477
477
478 def doc(self, obj):
478 def doc(self, obj):
479 if self._doc is None:
479 if self._doc is None:
480 return getattr(obj, self._name).__doc__
480 return getattr(obj, self._name).__doc__
481 return self._doc
481 return self._doc
482
482
483 def attrtype(self, obj):
483 def attrtype(self, obj):
484 return "itermethod"
484 return "itermethod"
485
485
486 def valuetype(self, obj):
486 def valuetype(self, obj):
487 return noitem
487 return noitem
488
488
489 def value(self, obj):
489 def value(self, obj):
490 return noitem
490 return noitem
491
491
492 def iter(self, obj):
492 def iter(self, obj):
493 return xiter(getattr(obj, self._name)())
493 return xiter(getattr(obj, self._name)())
494
494
495 def __repr__(self):
495 def __repr__(self):
496 if self._doc is None:
496 if self._doc is None:
497 return "IterMethod(%r)" % self._name
497 return "IterMethod(%r)" % self._name
498 else:
498 else:
499 return "IterMethod(%r, %r)" % (self._name, self._doc)
499 return "IterMethod(%r, %r)" % (self._name, self._doc)
500
500
501
501
502 class FunctionDescriptor(Descriptor):
502 class FunctionDescriptor(Descriptor):
503 """
503 """
504 A ``FunctionDescriptor`` turns a function into a descriptor. The function
504 A ``FunctionDescriptor`` turns a function into a descriptor. The function
505 will be called with the object to get the type and value of the attribute.
505 will be called with the object to get the type and value of the attribute.
506 """
506 """
507 __slots__ = ("_function", "_name", "_doc")
507 __slots__ = ("_function", "_name", "_doc")
508
508
509 def __init__(self, function, name=None, doc=None):
509 def __init__(self, function, name=None, doc=None):
510 self._function = function
510 self._function = function
511 self._name = name
511 self._name = name
512 self._doc = doc
512 self._doc = doc
513
513
514 def key(self):
514 def key(self):
515 return self._function
515 return self._function
516
516
517 def name(self):
517 def name(self):
518 if self._name is not None:
518 if self._name is not None:
519 return self._name
519 return self._name
520 return getattr(self._function, "__xname__", self._function.__name__)
520 return getattr(self._function, "__xname__", self._function.__name__)
521
521
522 def doc(self, obj):
522 def doc(self, obj):
523 if self._doc is None:
523 if self._doc is None:
524 return self._function.__doc__
524 return self._function.__doc__
525 return self._doc
525 return self._doc
526
526
527 def attrtype(self, obj):
527 def attrtype(self, obj):
528 return "function"
528 return "function"
529
529
530 def valuetype(self, obj):
530 def valuetype(self, obj):
531 return type(self._function(obj))
531 return type(self._function(obj))
532
532
533 def value(self, obj):
533 def value(self, obj):
534 return self._function(obj)
534 return self._function(obj)
535
535
536 def __repr__(self):
536 def __repr__(self):
537 if self._doc is None:
537 if self._doc is None:
538 return "Function(%r)" % self._name
538 return "Function(%r)" % self._name
539 else:
539 else:
540 return "Function(%r, %r)" % (self._name, self._doc)
540 return "Function(%r, %r)" % (self._name, self._doc)
541
541
542
542
543 class Table(object):
543 class Table(object):
544 """
544 """
545 A ``Table`` is an object that produces items (just like a normal Python
545 A ``Table`` is an object that produces items (just like a normal Python
546 iterator/generator does) and can be used as the first object in a pipeline
546 iterator/generator does) and can be used as the first object in a pipeline
547 expression. The displayhook will open the default browser for such an object
547 expression. The displayhook will open the default browser for such an object
548 (instead of simply printing the ``repr()`` result).
548 (instead of simply printing the ``repr()`` result).
549 """
549 """
550
550
551 # We want to support ``foo`` and ``foo()`` in pipeline expression:
551 # We want to support ``foo`` and ``foo()`` in pipeline expression:
552 # So we implement the required operators (``|`` and ``+``) in the metaclass,
552 # So we implement the required operators (``|`` and ``+``) in the metaclass,
553 # instantiate the class and forward the operator to the instance
553 # instantiate the class and forward the operator to the instance
554 class __metaclass__(type):
554 class __metaclass__(type):
555 def __iter__(self):
555 def __iter__(self):
556 return iter(self())
556 return iter(self())
557
557
558 def __or__(self, other):
558 def __or__(self, other):
559 return self() | other
559 return self() | other
560
560
561 def __add__(self, other):
561 def __add__(self, other):
562 return self() + other
562 return self() + other
563
563
564 def __radd__(self, other):
564 def __radd__(self, other):
565 return other + self()
565 return other + self()
566
566
567 def __getitem__(self, index):
567 def __getitem__(self, index):
568 return self()[index]
568 return self()[index]
569
569
570 def __getitem__(self, index):
570 def __getitem__(self, index):
571 return item(self, index)
571 return item(self, index)
572
572
573 def __contains__(self, item):
573 def __contains__(self, item):
574 for haveitem in self:
574 for haveitem in self:
575 if item == haveitem:
575 if item == haveitem:
576 return True
576 return True
577 return False
577 return False
578
578
579 def __or__(self, other):
579 def __or__(self, other):
580 # autoinstantiate right hand side
580 # autoinstantiate right hand side
581 if isinstance(other, type) and issubclass(other, (Table, Display)):
581 if isinstance(other, type) and issubclass(other, (Table, Display)):
582 other = other()
582 other = other()
583 # treat simple strings and functions as ``ieval`` instances
583 # treat simple strings and functions as ``ieval`` instances
584 elif not isinstance(other, Display) and not isinstance(other, Table):
584 elif not isinstance(other, Display) and not isinstance(other, Table):
585 other = ieval(other)
585 other = ieval(other)
586 # forward operations to the right hand side
586 # forward operations to the right hand side
587 return other.__ror__(self)
587 return other.__ror__(self)
588
588
589 def __add__(self, other):
589 def __add__(self, other):
590 # autoinstantiate right hand side
590 # autoinstantiate right hand side
591 if isinstance(other, type) and issubclass(other, Table):
591 if isinstance(other, type) and issubclass(other, Table):
592 other = other()
592 other = other()
593 return ichain(self, other)
593 return ichain(self, other)
594
594
595 def __radd__(self, other):
595 def __radd__(self, other):
596 # autoinstantiate left hand side
596 # autoinstantiate left hand side
597 if isinstance(other, type) and issubclass(other, Table):
597 if isinstance(other, type) and issubclass(other, Table):
598 other = other()
598 other = other()
599 return ichain(other, self)
599 return ichain(other, self)
600
600
601
601
602 class Pipe(Table):
602 class Pipe(Table):
603 """
603 """
604 A ``Pipe`` is an object that can be used in a pipeline expression. It
604 A ``Pipe`` is an object that can be used in a pipeline expression. It
605 processes the objects it gets from its input ``Table``/``Pipe``. Note that
605 processes the objects it gets from its input ``Table``/``Pipe``. Note that
606 a ``Pipe`` object can't be used as the first object in a pipeline
606 a ``Pipe`` object can't be used as the first object in a pipeline
607 expression, as it doesn't produces items itself.
607 expression, as it doesn't produces items itself.
608 """
608 """
609 class __metaclass__(Table.__metaclass__):
609 class __metaclass__(Table.__metaclass__):
610 def __ror__(self, input):
610 def __ror__(self, input):
611 return input | self()
611 return input | self()
612
612
613 def __ror__(self, input):
613 def __ror__(self, input):
614 # autoinstantiate left hand side
614 # autoinstantiate left hand side
615 if isinstance(input, type) and issubclass(input, Table):
615 if isinstance(input, type) and issubclass(input, Table):
616 input = input()
616 input = input()
617 self.input = input
617 self.input = input
618 return self
618 return self
619
619
620
620
621 def xrepr(item, mode="default"):
621 def xrepr(item, mode="default"):
622 """
622 """
623 Generic function that adds color output and different display modes to ``repr``.
623 Generic function that adds color output and different display modes to ``repr``.
624
624
625 The result of an ``xrepr`` call is iterable and consists of ``(style, string)``
625 The result of an ``xrepr`` call is iterable and consists of ``(style, string)``
626 tuples. The ``style`` in this tuple must be a ``Style`` object from the
626 tuples. The ``style`` in this tuple must be a ``Style`` object from the
627 ``astring`` module. To reconfigure the output the first yielded tuple can be
627 ``astring`` module. To reconfigure the output the first yielded tuple can be
628 a ``(aligment, full)`` tuple instead of a ``(style, string)`` tuple.
628 a ``(aligment, full)`` tuple instead of a ``(style, string)`` tuple.
629 ``alignment`` can be -1 for left aligned, 0 for centered and 1 for right
629 ``alignment`` can be -1 for left aligned, 0 for centered and 1 for right
630 aligned (the default is left alignment). ``full`` is a boolean that specifies
630 aligned (the default is left alignment). ``full`` is a boolean that specifies
631 whether the complete output must be displayed or the ``Display`` object is
631 whether the complete output must be displayed or the ``Display`` object is
632 allowed to stop output after enough text has been produced (e.g. a syntax
632 allowed to stop output after enough text has been produced (e.g. a syntax
633 highlighted text line would use ``True``, but for a large data structure
633 highlighted text line would use ``True``, but for a large data structure
634 (i.e. a nested list, tuple or dictionary) ``False`` would be used).
634 (i.e. a nested list, tuple or dictionary) ``False`` would be used).
635 The default is full output.
635 The default is full output.
636
636
637 There are four different possible values for ``mode`` depending on where
637 There are four different possible values for ``mode`` depending on where
638 the ``Display`` object will display ``item``:
638 the ``Display`` object will display ``item``:
639
639
640 * ``"header"``: ``item`` will be displayed in a header line (this is used by
640 * ``"header"``: ``item`` will be displayed in a header line (this is used by
641 ``ibrowse``).
641 ``ibrowse``).
642 * ``"footer"``: ``item`` will be displayed in a footer line (this is used by
642 * ``"footer"``: ``item`` will be displayed in a footer line (this is used by
643 ``ibrowse``).
643 ``ibrowse``).
644 * ``"cell"``: ``item`` will be displayed in a table cell/list.
644 * ``"cell"``: ``item`` will be displayed in a table cell/list.
645 * ``"default"``: default mode. If an ``xrepr`` implementation recursively
645 * ``"default"``: default mode. If an ``xrepr`` implementation recursively
646 outputs objects, ``"default"`` must be passed in the recursive calls to
646 outputs objects, ``"default"`` must be passed in the recursive calls to
647 ``xrepr``.
647 ``xrepr``.
648
648
649 If no implementation is registered for ``item``, ``xrepr`` will try the
649 If no implementation is registered for ``item``, ``xrepr`` will try the
650 ``__xrepr__`` method on ``item``. If ``item`` doesn't have an ``__xrepr__``
650 ``__xrepr__`` method on ``item``. If ``item`` doesn't have an ``__xrepr__``
651 method it falls back to ``repr``/``__repr__`` for all modes.
651 method it falls back to ``repr``/``__repr__`` for all modes.
652 """
652 """
653 try:
653 try:
654 func = item.__xrepr__
654 func = item.__xrepr__
655 except AttributeError:
655 except AttributeError:
656 yield (astyle.style_default, repr(item))
656 yield (astyle.style_default, repr(item))
657 else:
657 else:
658 try:
658 try:
659 for x in func(mode):
659 for x in func(mode):
660 yield x
660 yield x
661 except (KeyboardInterrupt, SystemExit):
661 except (KeyboardInterrupt, SystemExit):
662 raise
662 raise
663 except Exception:
663 except Exception:
664 yield (astyle.style_default, repr(item))
664 yield (astyle.style_default, repr(item))
665 xrepr = simplegeneric.generic(xrepr)
665 xrepr = simplegeneric.generic(xrepr)
666
666
667
667
668 def xrepr_none(self, mode="default"):
668 def xrepr_none(self, mode="default"):
669 yield (astyle.style_type_none, repr(self))
669 yield (astyle.style_type_none, repr(self))
670 xrepr.when_object(None)(xrepr_none)
670 xrepr.when_object(None)(xrepr_none)
671
671
672
672
673 def xrepr_bool(self, mode="default"):
673 def xrepr_bool(self, mode="default"):
674 yield (astyle.style_type_bool, repr(self))
674 yield (astyle.style_type_bool, repr(self))
675 xrepr.when_type(bool)(xrepr_bool)
675 xrepr.when_type(bool)(xrepr_bool)
676
676
677
677
678 def xrepr_str(self, mode="default"):
678 def xrepr_str(self, mode="default"):
679 if mode == "cell":
679 if mode == "cell":
680 yield (astyle.style_default, repr(self.expandtabs(tab))[1:-1])
680 yield (astyle.style_default, repr(self.expandtabs(tab))[1:-1])
681 else:
681 else:
682 yield (astyle.style_default, repr(self))
682 yield (astyle.style_default, repr(self))
683 xrepr.when_type(str)(xrepr_str)
683 xrepr.when_type(str)(xrepr_str)
684
684
685
685
686 def xrepr_unicode(self, mode="default"):
686 def xrepr_unicode(self, mode="default"):
687 if mode == "cell":
687 if mode == "cell":
688 yield (astyle.style_default, repr(self.expandtabs(tab))[2:-1])
688 yield (astyle.style_default, repr(self.expandtabs(tab))[2:-1])
689 else:
689 else:
690 yield (astyle.style_default, repr(self))
690 yield (astyle.style_default, repr(self))
691 xrepr.when_type(unicode)(xrepr_unicode)
691 xrepr.when_type(unicode)(xrepr_unicode)
692
692
693
693
694 def xrepr_number(self, mode="default"):
694 def xrepr_number(self, mode="default"):
695 yield (1, True)
695 yield (1, True)
696 yield (astyle.style_type_number, repr(self))
696 yield (astyle.style_type_number, repr(self))
697 xrepr.when_type(int)(xrepr_number)
697 xrepr.when_type(int)(xrepr_number)
698 xrepr.when_type(long)(xrepr_number)
698 xrepr.when_type(long)(xrepr_number)
699 xrepr.when_type(float)(xrepr_number)
699 xrepr.when_type(float)(xrepr_number)
700
700
701
701
702 def xrepr_complex(self, mode="default"):
702 def xrepr_complex(self, mode="default"):
703 yield (astyle.style_type_number, repr(self))
703 yield (astyle.style_type_number, repr(self))
704 xrepr.when_type(complex)(xrepr_number)
704 xrepr.when_type(complex)(xrepr_number)
705
705
706
706
707 def xrepr_datetime(self, mode="default"):
707 def xrepr_datetime(self, mode="default"):
708 if mode == "cell":
708 if mode == "cell":
709 # Don't use strftime() here, as this requires year >= 1900
709 # Don't use strftime() here, as this requires year >= 1900
710 yield (astyle.style_type_datetime,
710 yield (astyle.style_type_datetime,
711 "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
711 "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
712 (self.year, self.month, self.day,
712 (self.year, self.month, self.day,
713 self.hour, self.minute, self.second,
713 self.hour, self.minute, self.second,
714 self.microsecond),
714 self.microsecond),
715 )
715 )
716 else:
716 else:
717 yield (astyle.style_type_datetime, repr(self))
717 yield (astyle.style_type_datetime, repr(self))
718 xrepr.when_type(datetime.datetime)(xrepr_datetime)
718 xrepr.when_type(datetime.datetime)(xrepr_datetime)
719
719
720
720
721 def xrepr_date(self, mode="default"):
721 def xrepr_date(self, mode="default"):
722 if mode == "cell":
722 if mode == "cell":
723 yield (astyle.style_type_datetime,
723 yield (astyle.style_type_datetime,
724 "%04d-%02d-%02d" % (self.year, self.month, self.day))
724 "%04d-%02d-%02d" % (self.year, self.month, self.day))
725 else:
725 else:
726 yield (astyle.style_type_datetime, repr(self))
726 yield (astyle.style_type_datetime, repr(self))
727 xrepr.when_type(datetime.date)(xrepr_date)
727 xrepr.when_type(datetime.date)(xrepr_date)
728
728
729
729
730 def xrepr_time(self, mode="default"):
730 def xrepr_time(self, mode="default"):
731 if mode == "cell":
731 if mode == "cell":
732 yield (astyle.style_type_datetime,
732 yield (astyle.style_type_datetime,
733 "%02d:%02d:%02d.%06d" % \
733 "%02d:%02d:%02d.%06d" % \
734 (self.hour, self.minute, self.second, self.microsecond))
734 (self.hour, self.minute, self.second, self.microsecond))
735 else:
735 else:
736 yield (astyle.style_type_datetime, repr(self))
736 yield (astyle.style_type_datetime, repr(self))
737 xrepr.when_type(datetime.time)(xrepr_time)
737 xrepr.when_type(datetime.time)(xrepr_time)
738
738
739
739
740 def xrepr_timedelta(self, mode="default"):
740 def xrepr_timedelta(self, mode="default"):
741 yield (astyle.style_type_datetime, repr(self))
741 yield (astyle.style_type_datetime, repr(self))
742 xrepr.when_type(datetime.timedelta)(xrepr_timedelta)
742 xrepr.when_type(datetime.timedelta)(xrepr_timedelta)
743
743
744
744
745 def xrepr_type(self, mode="default"):
745 def xrepr_type(self, mode="default"):
746 if self.__module__ == "__builtin__":
746 if self.__module__ == "__builtin__":
747 yield (astyle.style_type_type, self.__name__)
747 yield (astyle.style_type_type, self.__name__)
748 else:
748 else:
749 yield (astyle.style_type_type, "%s.%s" % (self.__module__, self.__name__))
749 yield (astyle.style_type_type, "%s.%s" % (self.__module__, self.__name__))
750 xrepr.when_type(type)(xrepr_type)
750 xrepr.when_type(type)(xrepr_type)
751
751
752
752
753 def xrepr_exception(self, mode="default"):
753 def xrepr_exception(self, mode="default"):
754 if self.__class__.__module__ == "exceptions":
754 if self.__class__.__module__ == "exceptions":
755 classname = self.__class__.__name__
755 classname = self.__class__.__name__
756 else:
756 else:
757 classname = "%s.%s" % \
757 classname = "%s.%s" % \
758 (self.__class__.__module__, self.__class__.__name__)
758 (self.__class__.__module__, self.__class__.__name__)
759 if mode == "header" or mode == "footer":
759 if mode == "header" or mode == "footer":
760 yield (astyle.style_error, "%s: %s" % (classname, self))
760 yield (astyle.style_error, "%s: %s" % (classname, self))
761 else:
761 else:
762 yield (astyle.style_error, classname)
762 yield (astyle.style_error, classname)
763 xrepr.when_type(Exception)(xrepr_exception)
763 xrepr.when_type(Exception)(xrepr_exception)
764
764
765
765
766 def xrepr_listtuple(self, mode="default"):
766 def xrepr_listtuple(self, mode="default"):
767 if mode == "header" or mode == "footer":
767 if mode == "header" or mode == "footer":
768 if self.__class__.__module__ == "__builtin__":
768 if self.__class__.__module__ == "__builtin__":
769 classname = self.__class__.__name__
769 classname = self.__class__.__name__
770 else:
770 else:
771 classname = "%s.%s" % \
771 classname = "%s.%s" % \
772 (self.__class__.__module__,self.__class__.__name__)
772 (self.__class__.__module__,self.__class__.__name__)
773 yield (astyle.style_default,
773 yield (astyle.style_default,
774 "<%s object with %d items at 0x%x>" % \
774 "<%s object with %d items at 0x%x>" % \
775 (classname, len(self), id(self)))
775 (classname, len(self), id(self)))
776 else:
776 else:
777 yield (-1, False)
777 yield (-1, False)
778 if isinstance(self, list):
778 if isinstance(self, list):
779 yield (astyle.style_default, "[")
779 yield (astyle.style_default, "[")
780 end = "]"
780 end = "]"
781 else:
781 else:
782 yield (astyle.style_default, "(")
782 yield (astyle.style_default, "(")
783 end = ")"
783 end = ")"
784 for (i, subself) in enumerate(self):
784 for (i, subself) in enumerate(self):
785 if i:
785 if i:
786 yield (astyle.style_default, ", ")
786 yield (astyle.style_default, ", ")
787 for part in xrepr(subself, "default"):
787 for part in xrepr(subself, "default"):
788 yield part
788 yield part
789 yield (astyle.style_default, end)
789 yield (astyle.style_default, end)
790 xrepr.when_type(list)(xrepr_listtuple)
790 xrepr.when_type(list)(xrepr_listtuple)
791 xrepr.when_type(tuple)(xrepr_listtuple)
791 xrepr.when_type(tuple)(xrepr_listtuple)
792
792
793
793
794 def xrepr_dict(self, mode="default"):
794 def xrepr_dict(self, mode="default"):
795 if mode == "header" or mode == "footer":
795 if mode == "header" or mode == "footer":
796 if self.__class__.__module__ == "__builtin__":
796 if self.__class__.__module__ == "__builtin__":
797 classname = self.__class__.__name__
797 classname = self.__class__.__name__
798 else:
798 else:
799 classname = "%s.%s" % \
799 classname = "%s.%s" % \
800 (self.__class__.__module__,self.__class__.__name__)
800 (self.__class__.__module__,self.__class__.__name__)
801 yield (astyle.style_default,
801 yield (astyle.style_default,
802 "<%s object with %d items at 0x%x>" % \
802 "<%s object with %d items at 0x%x>" % \
803 (classname, len(self), id(self)))
803 (classname, len(self), id(self)))
804 else:
804 else:
805 yield (-1, False)
805 yield (-1, False)
806 if isinstance(self, dict):
806 if isinstance(self, dict):
807 yield (astyle.style_default, "{")
807 yield (astyle.style_default, "{")
808 end = "}"
808 end = "}"
809 else:
809 else:
810 yield (astyle.style_default, "dictproxy((")
810 yield (astyle.style_default, "dictproxy((")
811 end = "})"
811 end = "})"
812 for (i, (key, value)) in enumerate(self.iteritems()):
812 for (i, (key, value)) in enumerate(self.iteritems()):
813 if i:
813 if i:
814 yield (astyle.style_default, ", ")
814 yield (astyle.style_default, ", ")
815 for part in xrepr(key, "default"):
815 for part in xrepr(key, "default"):
816 yield part
816 yield part
817 yield (astyle.style_default, ": ")
817 yield (astyle.style_default, ": ")
818 for part in xrepr(value, "default"):
818 for part in xrepr(value, "default"):
819 yield part
819 yield part
820 yield (astyle.style_default, end)
820 yield (astyle.style_default, end)
821 xrepr.when_type(dict)(xrepr_dict)
821 xrepr.when_type(dict)(xrepr_dict)
822 xrepr.when_type(types.DictProxyType)(xrepr_dict)
822 xrepr.when_type(types.DictProxyType)(xrepr_dict)
823
823
824
824
825 def upgradexattr(attr):
825 def upgradexattr(attr):
826 """
826 """
827 Convert an attribute descriptor string to a real descriptor object.
827 Convert an attribute descriptor string to a real descriptor object.
828
828
829 If attr already is a descriptor object return if unmodified. A
829 If attr already is a descriptor object return if unmodified. A
830 ``SelfDescriptor`` will be returned if ``attr`` is ``None``. ``"foo"``
830 ``SelfDescriptor`` will be returned if ``attr`` is ``None``. ``"foo"``
831 returns an ``AttributeDescriptor`` for the attribute named ``"foo"``.
831 returns an ``AttributeDescriptor`` for the attribute named ``"foo"``.
832 ``"foo()"`` returns a ``MethodDescriptor`` for the method named ``"foo"``.
832 ``"foo()"`` returns a ``MethodDescriptor`` for the method named ``"foo"``.
833 ``"-foo"`` will return an ``IterAttributeDescriptor`` for the attribute
833 ``"-foo"`` will return an ``IterAttributeDescriptor`` for the attribute
834 named ``"foo"`` and ``"-foo()"`` will return an ``IterMethodDescriptor``
834 named ``"foo"`` and ``"-foo()"`` will return an ``IterMethodDescriptor``
835 for the method named ``"foo"``. Furthermore integer will return the appropriate
835 for the method named ``"foo"``. Furthermore integer will return the appropriate
836 ``IndexDescriptor`` and callables will return a ``FunctionDescriptor``.
836 ``IndexDescriptor`` and callables will return a ``FunctionDescriptor``.
837 """
837 """
838 if attr is None:
838 if attr is None:
839 return selfdescriptor
839 return selfdescriptor
840 elif isinstance(attr, Descriptor):
840 elif isinstance(attr, Descriptor):
841 return attr
841 return attr
842 elif isinstance(attr, str):
842 elif isinstance(attr, str):
843 if attr.endswith("()"):
843 if attr.endswith("()"):
844 if attr.startswith("-"):
844 if attr.startswith("-"):
845 return IterMethodDescriptor(attr[1:-2])
845 return IterMethodDescriptor(attr[1:-2])
846 else:
846 else:
847 return MethodDescriptor(attr[:-2])
847 return MethodDescriptor(attr[:-2])
848 else:
848 else:
849 if attr.startswith("-"):
849 if attr.startswith("-"):
850 return IterAttributeDescriptor(attr[1:])
850 return IterAttributeDescriptor(attr[1:])
851 else:
851 else:
852 return AttributeDescriptor(attr)
852 return AttributeDescriptor(attr)
853 elif isinstance(attr, (int, long)):
853 elif isinstance(attr, (int, long)):
854 return IndexDescriptor(attr)
854 return IndexDescriptor(attr)
855 elif callable(attr):
855 elif callable(attr):
856 return FunctionDescriptor(attr)
856 return FunctionDescriptor(attr)
857 else:
857 else:
858 raise TypeError("can't handle descriptor %r" % attr)
858 raise TypeError("can't handle descriptor %r" % attr)
859
859
860
860
861 def xattrs(item, mode="default"):
861 def xattrs(item, mode="default"):
862 """
862 """
863 Generic function that returns an iterable of attribute descriptors
863 Generic function that returns an iterable of attribute descriptors
864 to be used for displaying the attributes ob the object ``item`` in display
864 to be used for displaying the attributes ob the object ``item`` in display
865 mode ``mode``.
865 mode ``mode``.
866
866
867 There are two possible modes:
867 There are two possible modes:
868
868
869 * ``"detail"``: The ``Display`` object wants to display a detailed list
869 * ``"detail"``: The ``Display`` object wants to display a detailed list
870 of the object attributes.
870 of the object attributes.
871 * ``"default"``: The ``Display`` object wants to display the object in a
871 * ``"default"``: The ``Display`` object wants to display the object in a
872 list view.
872 list view.
873
873
874 If no implementation is registered for the object ``item`` ``xattrs`` falls
874 If no implementation is registered for the object ``item`` ``xattrs`` falls
875 back to trying the ``__xattrs__`` method of the object. If this doesn't
875 back to trying the ``__xattrs__`` method of the object. If this doesn't
876 exist either, ``dir(item)`` is used for ``"detail"`` mode and ``(None,)``
876 exist either, ``dir(item)`` is used for ``"detail"`` mode and ``(None,)``
877 for ``"default"`` mode.
877 for ``"default"`` mode.
878
878
879 The implementation must yield attribute descriptor (see the class
879 The implementation must yield attribute descriptor (see the class
880 ``Descriptor`` for more info). The ``__xattrs__`` method may also return
880 ``Descriptor`` for more info). The ``__xattrs__`` method may also return
881 attribute descriptor string (and ``None``) which will be converted to real
881 attribute descriptor string (and ``None``) which will be converted to real
882 descriptors by ``upgradexattr()``.
882 descriptors by ``upgradexattr()``.
883 """
883 """
884 try:
884 try:
885 func = item.__xattrs__
885 func = item.__xattrs__
886 except AttributeError:
886 except AttributeError:
887 if mode == "detail":
887 if mode == "detail":
888 for attrname in dir(item):
888 for attrname in dir(item):
889 yield AttributeDescriptor(attrname)
889 yield AttributeDescriptor(attrname)
890 else:
890 else:
891 yield selfdescriptor
891 yield selfdescriptor
892 else:
892 else:
893 for attr in func(mode):
893 for attr in func(mode):
894 yield upgradexattr(attr)
894 yield upgradexattr(attr)
895 xattrs = simplegeneric.generic(xattrs)
895 xattrs = simplegeneric.generic(xattrs)
896
896
897
897
898 def xattrs_complex(self, mode="default"):
898 def xattrs_complex(self, mode="default"):
899 if mode == "detail":
899 if mode == "detail":
900 return (AttributeDescriptor("real"), AttributeDescriptor("imag"))
900 return (AttributeDescriptor("real"), AttributeDescriptor("imag"))
901 return (selfdescriptor,)
901 return (selfdescriptor,)
902 xattrs.when_type(complex)(xattrs_complex)
902 xattrs.when_type(complex)(xattrs_complex)
903
903
904
904
905 def _isdict(item):
905 def _isdict(item):
906 try:
906 try:
907 itermeth = item.__class__.__iter__
907 itermeth = item.__class__.__iter__
908 except (AttributeError, TypeError):
908 except (AttributeError, TypeError):
909 return False
909 return False
910 return itermeth is dict.__iter__ or itermeth is types.DictProxyType.__iter__
910 return itermeth is dict.__iter__ or itermeth is types.DictProxyType.__iter__
911
911
912
912
913 def _isstr(item):
913 def _isstr(item):
914 if not isinstance(item, basestring):
914 if not isinstance(item, basestring):
915 return False
915 return False
916 try:
916 try:
917 itermeth = item.__class__.__iter__
917 itermeth = item.__class__.__iter__
918 except AttributeError:
918 except AttributeError:
919 return True
919 return True
920 return False # ``__iter__`` has been redefined
920 return False # ``__iter__`` has been redefined
921
921
922
922
923 def xiter(item):
923 def xiter(item):
924 """
924 """
925 Generic function that implements iteration for pipeline expression. If no
925 Generic function that implements iteration for pipeline expression. If no
926 implementation is registered for ``item`` ``xiter`` falls back to ``iter``.
926 implementation is registered for ``item`` ``xiter`` falls back to ``iter``.
927 """
927 """
928 try:
928 try:
929 func = item.__xiter__
929 func = item.__xiter__
930 except AttributeError:
930 except AttributeError:
931 if _isdict(item):
931 if _isdict(item):
932 def items(item):
932 def items(item):
933 fields = ("key", "value")
933 fields = ("key", "value")
934 for (key, value) in item.iteritems():
934 for (key, value) in item.iteritems():
935 yield Fields(fields, key=key, value=value)
935 yield Fields(fields, key=key, value=value)
936 return items(item)
936 return items(item)
937 elif isinstance(item, new.module):
937 elif isinstance(item, new.module):
938 def items(item):
938 def items(item):
939 fields = ("key", "value")
939 fields = ("key", "value")
940 for key in sorted(item.__dict__):
940 for key in sorted(item.__dict__):
941 yield Fields(fields, key=key, value=getattr(item, key))
941 yield Fields(fields, key=key, value=getattr(item, key))
942 return items(item)
942 return items(item)
943 elif _isstr(item):
943 elif _isstr(item):
944 if not item:
944 if not item:
945 raise ValueError("can't enter empty string")
945 raise ValueError("can't enter empty string")
946 return iter(item.splitlines())
946 return iter(item.splitlines())
947 return iter(item)
947 return iter(item)
948 else:
948 else:
949 return iter(func()) # iter() just to be safe
949 return iter(func()) # iter() just to be safe
950 xiter = simplegeneric.generic(xiter)
950 xiter = simplegeneric.generic(xiter)
951
951
952
952
953 class ichain(Pipe):
953 class ichain(Pipe):
954 """
954 """
955 Chains multiple ``Table``s into one.
955 Chains multiple ``Table``s into one.
956 """
956 """
957
957
958 def __init__(self, *iters):
958 def __init__(self, *iters):
959 self.iters = iters
959 self.iters = iters
960
960
961 def __iter__(self):
961 def __iter__(self):
962 return itertools.chain(*self.iters)
962 return itertools.chain(*self.iters)
963
963
964 def __xrepr__(self, mode="default"):
964 def __xrepr__(self, mode="default"):
965 if mode == "header" or mode == "footer":
965 if mode == "header" or mode == "footer":
966 for (i, item) in enumerate(self.iters):
966 for (i, item) in enumerate(self.iters):
967 if i:
967 if i:
968 yield (astyle.style_default, "+")
968 yield (astyle.style_default, "+")
969 if isinstance(item, Pipe):
969 if isinstance(item, Pipe):
970 yield (astyle.style_default, "(")
970 yield (astyle.style_default, "(")
971 for part in xrepr(item, mode):
971 for part in xrepr(item, mode):
972 yield part
972 yield part
973 if isinstance(item, Pipe):
973 if isinstance(item, Pipe):
974 yield (astyle.style_default, ")")
974 yield (astyle.style_default, ")")
975 else:
975 else:
976 yield (astyle.style_default, repr(self))
976 yield (astyle.style_default, repr(self))
977
977
978 def __repr__(self):
978 def __repr__(self):
979 args = ", ".join([repr(it) for it in self.iters])
979 args = ", ".join([repr(it) for it in self.iters])
980 return "%s.%s(%s)" % \
980 return "%s.%s(%s)" % \
981 (self.__class__.__module__, self.__class__.__name__, args)
981 (self.__class__.__module__, self.__class__.__name__, args)
982
982
983
983
984 class ifile(path.path):
984 class ifile(path.path):
985 """
985 """
986 file (or directory) object.
986 file (or directory) object.
987 """
987 """
988
988
989 def getmode(self):
989 def getmode(self):
990 return self.stat().st_mode
990 return self.stat().st_mode
991 mode = property(getmode, None, None, "Access mode")
991 mode = property(getmode, None, None, "Access mode")
992
992
993 def gettype(self):
993 def gettype(self):
994 data = [
994 data = [
995 (stat.S_ISREG, "file"),
995 (stat.S_ISREG, "file"),
996 (stat.S_ISDIR, "dir"),
996 (stat.S_ISDIR, "dir"),
997 (stat.S_ISCHR, "chardev"),
997 (stat.S_ISCHR, "chardev"),
998 (stat.S_ISBLK, "blockdev"),
998 (stat.S_ISBLK, "blockdev"),
999 (stat.S_ISFIFO, "fifo"),
999 (stat.S_ISFIFO, "fifo"),
1000 (stat.S_ISLNK, "symlink"),
1000 (stat.S_ISLNK, "symlink"),
1001 (stat.S_ISSOCK,"socket"),
1001 (stat.S_ISSOCK,"socket"),
1002 ]
1002 ]
1003 lstat = self.lstat()
1003 lstat = self.lstat()
1004 if lstat is not None:
1004 if lstat is not None:
1005 types = set([text for (func, text) in data if func(lstat.st_mode)])
1005 types = set([text for (func, text) in data if func(lstat.st_mode)])
1006 else:
1006 else:
1007 types = set()
1007 types = set()
1008 m = self.mode
1008 m = self.mode
1009 types.update([text for (func, text) in data if func(m)])
1009 types.update([text for (func, text) in data if func(m)])
1010 return ", ".join(types)
1010 return ", ".join(types)
1011 type = property(gettype, None, None, "file type (file, directory, link, etc.)")
1011 type = property(gettype, None, None, "file type (file, directory, link, etc.)")
1012
1012
1013 def getmodestr(self):
1013 def getmodestr(self):
1014 m = self.mode
1014 m = self.mode
1015 data = [
1015 data = [
1016 (stat.S_IRUSR, "-r"),
1016 (stat.S_IRUSR, "-r"),
1017 (stat.S_IWUSR, "-w"),
1017 (stat.S_IWUSR, "-w"),
1018 (stat.S_IXUSR, "-x"),
1018 (stat.S_IXUSR, "-x"),
1019 (stat.S_IRGRP, "-r"),
1019 (stat.S_IRGRP, "-r"),
1020 (stat.S_IWGRP, "-w"),
1020 (stat.S_IWGRP, "-w"),
1021 (stat.S_IXGRP, "-x"),
1021 (stat.S_IXGRP, "-x"),
1022 (stat.S_IROTH, "-r"),
1022 (stat.S_IROTH, "-r"),
1023 (stat.S_IWOTH, "-w"),
1023 (stat.S_IWOTH, "-w"),
1024 (stat.S_IXOTH, "-x"),
1024 (stat.S_IXOTH, "-x"),
1025 ]
1025 ]
1026 return "".join([text[bool(m&bit)] for (bit, text) in data])
1026 return "".join([text[bool(m&bit)] for (bit, text) in data])
1027
1027
1028 modestr = property(getmodestr, None, None, "Access mode as string")
1028 modestr = property(getmodestr, None, None, "Access mode as string")
1029
1029
1030 def getblocks(self):
1030 def getblocks(self):
1031 return self.stat().st_blocks
1031 return self.stat().st_blocks
1032 blocks = property(getblocks, None, None, "File size in blocks")
1032 blocks = property(getblocks, None, None, "File size in blocks")
1033
1033
1034 def getblksize(self):
1034 def getblksize(self):
1035 return self.stat().st_blksize
1035 return self.stat().st_blksize
1036 blksize = property(getblksize, None, None, "Filesystem block size")
1036 blksize = property(getblksize, None, None, "Filesystem block size")
1037
1037
1038 def getdev(self):
1038 def getdev(self):
1039 return self.stat().st_dev
1039 return self.stat().st_dev
1040 dev = property(getdev)
1040 dev = property(getdev)
1041
1041
1042 def getnlink(self):
1042 def getnlink(self):
1043 return self.stat().st_nlink
1043 return self.stat().st_nlink
1044 nlink = property(getnlink, None, None, "Number of links")
1044 nlink = property(getnlink, None, None, "Number of links")
1045
1045
1046 def getuid(self):
1046 def getuid(self):
1047 return self.stat().st_uid
1047 return self.stat().st_uid
1048 uid = property(getuid, None, None, "User id of file owner")
1048 uid = property(getuid, None, None, "User id of file owner")
1049
1049
1050 def getgid(self):
1050 def getgid(self):
1051 return self.stat().st_gid
1051 return self.stat().st_gid
1052 gid = property(getgid, None, None, "Group id of file owner")
1052 gid = property(getgid, None, None, "Group id of file owner")
1053
1053
1054 def getowner(self):
1054 def getowner(self):
1055 stat = self.stat()
1055 stat = self.stat()
1056 try:
1056 try:
1057 return pwd.getpwuid(stat.st_uid).pw_name
1057 return pwd.getpwuid(stat.st_uid).pw_name
1058 except KeyError:
1058 except KeyError:
1059 return stat.st_uid
1059 return stat.st_uid
1060 owner = property(getowner, None, None, "Owner name (or id)")
1060 owner = property(getowner, None, None, "Owner name (or id)")
1061
1061
1062 def getgroup(self):
1062 def getgroup(self):
1063 stat = self.stat()
1063 stat = self.stat()
1064 try:
1064 try:
1065 return grp.getgrgid(stat.st_gid).gr_name
1065 return grp.getgrgid(stat.st_gid).gr_name
1066 except KeyError:
1066 except KeyError:
1067 return stat.st_gid
1067 return stat.st_gid
1068 group = property(getgroup, None, None, "Group name (or id)")
1068 group = property(getgroup, None, None, "Group name (or id)")
1069
1069
1070 def getadate(self):
1070 def getadate(self):
1071 return datetime.datetime.utcfromtimestamp(self.atime)
1071 return datetime.datetime.utcfromtimestamp(self.atime)
1072 adate = property(getadate, None, None, "Access date")
1072 adate = property(getadate, None, None, "Access date")
1073
1073
1074 def getcdate(self):
1074 def getcdate(self):
1075 return datetime.datetime.utcfromtimestamp(self.ctime)
1075 return datetime.datetime.utcfromtimestamp(self.ctime)
1076 cdate = property(getcdate, None, None, "Creation date")
1076 cdate = property(getcdate, None, None, "Creation date")
1077
1077
1078 def getmdate(self):
1078 def getmdate(self):
1079 return datetime.datetime.utcfromtimestamp(self.mtime)
1079 return datetime.datetime.utcfromtimestamp(self.mtime)
1080 mdate = property(getmdate, None, None, "Modification date")
1080 mdate = property(getmdate, None, None, "Modification date")
1081
1081
1082 def mimetype(self):
1082 def mimetype(self):
1083 """
1083 """
1084 Return MIME type guessed from the extension.
1084 Return MIME type guessed from the extension.
1085 """
1085 """
1086 return mimetypes.guess_type(self.basename())[0]
1086 return mimetypes.guess_type(self.basename())[0]
1087
1087
1088 def encoding(self):
1088 def encoding(self):
1089 """
1089 """
1090 Return guessed compression (like "compress" or "gzip").
1090 Return guessed compression (like "compress" or "gzip").
1091 """
1091 """
1092 return mimetypes.guess_type(self.basename())[1]
1092 return mimetypes.guess_type(self.basename())[1]
1093
1093
1094 def __repr__(self):
1094 def __repr__(self):
1095 return "ifile(%s)" % path._base.__repr__(self)
1095 return "ifile(%s)" % path._base.__repr__(self)
1096
1096
1097 if sys.platform == "win32":
1097 if sys.platform == "win32":
1098 defaultattrs = (None, "type", "size", "modestr", "mdate")
1098 defaultattrs = (None, "type", "size", "modestr", "mdate")
1099 else:
1099 else:
1100 defaultattrs = (None, "type", "size", "modestr", "owner", "group", "mdate")
1100 defaultattrs = (None, "type", "size", "modestr", "owner", "group", "mdate")
1101
1101
1102 def __xattrs__(self, mode="default"):
1102 def __xattrs__(self, mode="default"):
1103 if mode == "detail":
1103 if mode == "detail":
1104 return (
1104 return (
1105 "name",
1105 "name",
1106 "basename()",
1106 "basename()",
1107 "abspath()",
1107 "abspath()",
1108 "realpath()",
1108 "realpath()",
1109 "type",
1109 "type",
1110 "mode",
1110 "mode",
1111 "modestr",
1111 "modestr",
1112 "stat()",
1112 "stat()",
1113 "lstat()",
1113 "lstat()",
1114 "uid",
1114 "uid",
1115 "gid",
1115 "gid",
1116 "owner",
1116 "owner",
1117 "group",
1117 "group",
1118 "dev",
1118 "dev",
1119 "nlink",
1119 "nlink",
1120 "ctime",
1120 "ctime",
1121 "mtime",
1121 "mtime",
1122 "atime",
1122 "atime",
1123 "cdate",
1123 "cdate",
1124 "mdate",
1124 "mdate",
1125 "adate",
1125 "adate",
1126 "size",
1126 "size",
1127 "blocks",
1127 "blocks",
1128 "blksize",
1128 "blksize",
1129 "isdir()",
1129 "isdir()",
1130 "islink()",
1130 "islink()",
1131 "mimetype()",
1131 "mimetype()",
1132 "encoding()",
1132 "encoding()",
1133 "-listdir()",
1133 "-listdir()",
1134 "-dirs()",
1134 "-dirs()",
1135 "-files()",
1135 "-files()",
1136 "-walk()",
1136 "-walk()",
1137 "-walkdirs()",
1137 "-walkdirs()",
1138 "-walkfiles()",
1138 "-walkfiles()",
1139 )
1139 )
1140 else:
1140 else:
1141 return self.defaultattrs
1141 return self.defaultattrs
1142
1142
1143
1143
1144 def xiter_ifile(self):
1144 def xiter_ifile(self):
1145 if self.isdir():
1145 if self.isdir():
1146 yield (self / os.pardir).abspath()
1146 yield (self / os.pardir).abspath()
1147 for child in sorted(self.listdir()):
1147 for child in sorted(self.listdir()):
1148 yield child
1148 yield child
1149 else:
1149 else:
1150 f = self.open("rb")
1150 f = self.open("rb")
1151 for line in f:
1151 for line in f:
1152 yield line
1152 yield line
1153 f.close()
1153 f.close()
1154 xiter.when_type(ifile)(xiter_ifile)
1154 xiter.when_type(ifile)(xiter_ifile)
1155
1155
1156
1156
1157 # We need to implement ``xrepr`` for ``ifile`` as a generic function, because
1157 # We need to implement ``xrepr`` for ``ifile`` as a generic function, because
1158 # otherwise ``xrepr_str`` would kick in.
1158 # otherwise ``xrepr_str`` would kick in.
1159 def xrepr_ifile(self, mode="default"):
1159 def xrepr_ifile(self, mode="default"):
1160 try:
1160 try:
1161 if self.isdir():
1161 if self.isdir():
1162 name = "idir"
1162 name = "idir"
1163 style = astyle.style_dir
1163 style = astyle.style_dir
1164 else:
1164 else:
1165 name = "ifile"
1165 name = "ifile"
1166 style = astyle.style_file
1166 style = astyle.style_file
1167 except IOError:
1167 except IOError:
1168 name = "ifile"
1168 name = "ifile"
1169 style = astyle.style_default
1169 style = astyle.style_default
1170 if mode == "cell" or mode in "header" or mode == "footer":
1170 if mode == "cell" or mode in "header" or mode == "footer":
1171 abspath = repr(path._base(self.normpath()))
1171 abspath = repr(path._base(self.normpath()))
1172 if abspath.startswith("u"):
1172 if abspath.startswith("u"):
1173 abspath = abspath[2:-1]
1173 abspath = abspath[2:-1]
1174 else:
1174 else:
1175 abspath = abspath[1:-1]
1175 abspath = abspath[1:-1]
1176 if mode == "cell":
1176 if mode == "cell":
1177 yield (style, abspath)
1177 yield (style, abspath)
1178 else:
1178 else:
1179 yield (style, "%s(%s)" % (name, abspath))
1179 yield (style, "%s(%s)" % (name, abspath))
1180 else:
1180 else:
1181 yield (style, repr(self))
1181 yield (style, repr(self))
1182 xrepr.when_type(ifile)(xrepr_ifile)
1182 xrepr.when_type(ifile)(xrepr_ifile)
1183
1183
1184
1184
1185 class ils(Table):
1185 class ils(Table):
1186 """
1186 """
1187 List the current (or a specified) directory.
1187 List the current (or a specified) directory.
1188
1188
1189 Examples:
1189 Examples:
1190
1190
1191 >>> ils
1191 >>> ils
1192 >>> ils("/usr/local/lib/python2.4")
1192 >>> ils("/usr/local/lib/python2.4")
1193 >>> ils("~")
1193 >>> ils("~")
1194 """
1194 """
1195 def __init__(self, base=os.curdir, dirs=True, files=True):
1195 def __init__(self, base=os.curdir, dirs=True, files=True):
1196 self.base = os.path.expanduser(base)
1196 self.base = os.path.expanduser(base)
1197 self.dirs = dirs
1197 self.dirs = dirs
1198 self.files = files
1198 self.files = files
1199
1199
1200 def __iter__(self):
1200 def __iter__(self):
1201 base = ifile(self.base)
1201 base = ifile(self.base)
1202 yield (base / os.pardir).abspath()
1202 yield (base / os.pardir).abspath()
1203 for child in base.listdir():
1203 for child in base.listdir():
1204 if self.dirs:
1204 if self.dirs:
1205 if self.files:
1205 if self.files:
1206 yield child
1206 yield child
1207 else:
1207 else:
1208 if child.isdir():
1208 if child.isdir():
1209 yield child
1209 yield child
1210 elif self.files:
1210 elif self.files:
1211 if not child.isdir():
1211 if not child.isdir():
1212 yield child
1212 yield child
1213
1213
1214 def __xrepr__(self, mode="default"):
1214 def __xrepr__(self, mode="default"):
1215 return ifile(self.base).__xrepr__(mode)
1215 return xrepr(ifile(self.base), mode)
1216
1216
1217 def __repr__(self):
1217 def __repr__(self):
1218 return "%s.%s(%r)" % \
1218 return "%s.%s(%r)" % \
1219 (self.__class__.__module__, self.__class__.__name__, self.base)
1219 (self.__class__.__module__, self.__class__.__name__, self.base)
1220
1220
1221
1221
1222 class iglob(Table):
1222 class iglob(Table):
1223 """
1223 """
1224 List all files and directories matching a specified pattern.
1224 List all files and directories matching a specified pattern.
1225 (See ``glob.glob()`` for more info.).
1225 (See ``glob.glob()`` for more info.).
1226
1226
1227 Examples:
1227 Examples:
1228
1228
1229 >>> iglob("*.py")
1229 >>> iglob("*.py")
1230 """
1230 """
1231 def __init__(self, glob):
1231 def __init__(self, glob):
1232 self.glob = glob
1232 self.glob = glob
1233
1233
1234 def __iter__(self):
1234 def __iter__(self):
1235 for name in glob.glob(self.glob):
1235 for name in glob.glob(self.glob):
1236 yield ifile(name)
1236 yield ifile(name)
1237
1237
1238 def __xrepr__(self, mode="default"):
1238 def __xrepr__(self, mode="default"):
1239 if mode == "header" or mode == "footer" or mode == "cell":
1239 if mode == "header" or mode == "footer" or mode == "cell":
1240 yield (astyle.style_default,
1240 yield (astyle.style_default,
1241 "%s(%r)" % (self.__class__.__name__, self.glob))
1241 "%s(%r)" % (self.__class__.__name__, self.glob))
1242 else:
1242 else:
1243 yield (astyle.style_default, repr(self))
1243 yield (astyle.style_default, repr(self))
1244
1244
1245 def __repr__(self):
1245 def __repr__(self):
1246 return "%s.%s(%r)" % \
1246 return "%s.%s(%r)" % \
1247 (self.__class__.__module__, self.__class__.__name__, self.glob)
1247 (self.__class__.__module__, self.__class__.__name__, self.glob)
1248
1248
1249
1249
1250 class iwalk(Table):
1250 class iwalk(Table):
1251 """
1251 """
1252 List all files and directories in a directory and it's subdirectory.
1252 List all files and directories in a directory and it's subdirectory.
1253
1253
1254 >>> iwalk
1254 >>> iwalk
1255 >>> iwalk("/usr/local/lib/python2.4")
1255 >>> iwalk("/usr/local/lib/python2.4")
1256 >>> iwalk("~")
1256 >>> iwalk("~")
1257 """
1257 """
1258 def __init__(self, base=os.curdir, dirs=True, files=True):
1258 def __init__(self, base=os.curdir, dirs=True, files=True):
1259 self.base = os.path.expanduser(base)
1259 self.base = os.path.expanduser(base)
1260 self.dirs = dirs
1260 self.dirs = dirs
1261 self.files = files
1261 self.files = files
1262
1262
1263 def __iter__(self):
1263 def __iter__(self):
1264 for (dirpath, dirnames, filenames) in os.walk(self.base):
1264 for (dirpath, dirnames, filenames) in os.walk(self.base):
1265 if self.dirs:
1265 if self.dirs:
1266 for name in sorted(dirnames):
1266 for name in sorted(dirnames):
1267 yield ifile(os.path.join(dirpath, name))
1267 yield ifile(os.path.join(dirpath, name))
1268 if self.files:
1268 if self.files:
1269 for name in sorted(filenames):
1269 for name in sorted(filenames):
1270 yield ifile(os.path.join(dirpath, name))
1270 yield ifile(os.path.join(dirpath, name))
1271
1271
1272 def __xrepr__(self, mode="default"):
1272 def __xrepr__(self, mode="default"):
1273 if mode == "header" or mode == "footer" or mode == "cell":
1273 if mode == "header" or mode == "footer" or mode == "cell":
1274 yield (astyle.style_default,
1274 yield (astyle.style_default,
1275 "%s(%r)" % (self.__class__.__name__, self.base))
1275 "%s(%r)" % (self.__class__.__name__, self.base))
1276 else:
1276 else:
1277 yield (astyle.style_default, repr(self))
1277 yield (astyle.style_default, repr(self))
1278
1278
1279 def __repr__(self):
1279 def __repr__(self):
1280 return "%s.%s(%r)" % \
1280 return "%s.%s(%r)" % \
1281 (self.__class__.__module__, self.__class__.__name__, self.base)
1281 (self.__class__.__module__, self.__class__.__name__, self.base)
1282
1282
1283
1283
1284 class ipwdentry(object):
1284 class ipwdentry(object):
1285 """
1285 """
1286 ``ipwdentry`` objects encapsulate entries in the Unix user account and
1286 ``ipwdentry`` objects encapsulate entries in the Unix user account and
1287 password database.
1287 password database.
1288 """
1288 """
1289 def __init__(self, id):
1289 def __init__(self, id):
1290 self._id = id
1290 self._id = id
1291 self._entry = None
1291 self._entry = None
1292
1292
1293 def _getentry(self):
1293 def _getentry(self):
1294 if self._entry is None:
1294 if self._entry is None:
1295 if isinstance(self._id, basestring):
1295 if isinstance(self._id, basestring):
1296 self._entry = pwd.getpwnam(self._id)
1296 self._entry = pwd.getpwnam(self._id)
1297 else:
1297 else:
1298 self._entry = pwd.getpwuid(self._id)
1298 self._entry = pwd.getpwuid(self._id)
1299 return self._entry
1299 return self._entry
1300
1300
1301 def getname(self):
1301 def getname(self):
1302 if isinstance(self._id, basestring):
1302 if isinstance(self._id, basestring):
1303 return self._id
1303 return self._id
1304 else:
1304 else:
1305 return self._getentry().pw_name
1305 return self._getentry().pw_name
1306 name = property(getname, None, None, "User name")
1306 name = property(getname, None, None, "User name")
1307
1307
1308 def getpasswd(self):
1308 def getpasswd(self):
1309 return self._getentry().pw_passwd
1309 return self._getentry().pw_passwd
1310 passwd = property(getpasswd, None, None, "Password")
1310 passwd = property(getpasswd, None, None, "Password")
1311
1311
1312 def getuid(self):
1312 def getuid(self):
1313 if isinstance(self._id, basestring):
1313 if isinstance(self._id, basestring):
1314 return self._getentry().pw_uid
1314 return self._getentry().pw_uid
1315 else:
1315 else:
1316 return self._id
1316 return self._id
1317 uid = property(getuid, None, None, "User id")
1317 uid = property(getuid, None, None, "User id")
1318
1318
1319 def getgid(self):
1319 def getgid(self):
1320 return self._getentry().pw_gid
1320 return self._getentry().pw_gid
1321 gid = property(getgid, None, None, "Primary group id")
1321 gid = property(getgid, None, None, "Primary group id")
1322
1322
1323 def getgroup(self):
1323 def getgroup(self):
1324 return igrpentry(self.gid)
1324 return igrpentry(self.gid)
1325 group = property(getgroup, None, None, "Group")
1325 group = property(getgroup, None, None, "Group")
1326
1326
1327 def getgecos(self):
1327 def getgecos(self):
1328 return self._getentry().pw_gecos
1328 return self._getentry().pw_gecos
1329 gecos = property(getgecos, None, None, "Information (e.g. full user name)")
1329 gecos = property(getgecos, None, None, "Information (e.g. full user name)")
1330
1330
1331 def getdir(self):
1331 def getdir(self):
1332 return self._getentry().pw_dir
1332 return self._getentry().pw_dir
1333 dir = property(getdir, None, None, "$HOME directory")
1333 dir = property(getdir, None, None, "$HOME directory")
1334
1334
1335 def getshell(self):
1335 def getshell(self):
1336 return self._getentry().pw_shell
1336 return self._getentry().pw_shell
1337 shell = property(getshell, None, None, "Login shell")
1337 shell = property(getshell, None, None, "Login shell")
1338
1338
1339 def __xattrs__(self, mode="default"):
1339 def __xattrs__(self, mode="default"):
1340 return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell")
1340 return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell")
1341
1341
1342 def __repr__(self):
1342 def __repr__(self):
1343 return "%s.%s(%r)" % \
1343 return "%s.%s(%r)" % \
1344 (self.__class__.__module__, self.__class__.__name__, self._id)
1344 (self.__class__.__module__, self.__class__.__name__, self._id)
1345
1345
1346
1346
1347 class ipwd(Table):
1347 class ipwd(Table):
1348 """
1348 """
1349 List all entries in the Unix user account and password database.
1349 List all entries in the Unix user account and password database.
1350
1350
1351 Example:
1351 Example:
1352
1352
1353 >>> ipwd | isort("uid")
1353 >>> ipwd | isort("uid")
1354 """
1354 """
1355 def __iter__(self):
1355 def __iter__(self):
1356 for entry in pwd.getpwall():
1356 for entry in pwd.getpwall():
1357 yield ipwdentry(entry.pw_name)
1357 yield ipwdentry(entry.pw_name)
1358
1358
1359 def __xrepr__(self, mode="default"):
1359 def __xrepr__(self, mode="default"):
1360 if mode == "header" or mode == "footer" or mode == "cell":
1360 if mode == "header" or mode == "footer" or mode == "cell":
1361 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1361 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1362 else:
1362 else:
1363 yield (astyle.style_default, repr(self))
1363 yield (astyle.style_default, repr(self))
1364
1364
1365
1365
1366 class igrpentry(object):
1366 class igrpentry(object):
1367 """
1367 """
1368 ``igrpentry`` objects encapsulate entries in the Unix group database.
1368 ``igrpentry`` objects encapsulate entries in the Unix group database.
1369 """
1369 """
1370 def __init__(self, id):
1370 def __init__(self, id):
1371 self._id = id
1371 self._id = id
1372 self._entry = None
1372 self._entry = None
1373
1373
1374 def _getentry(self):
1374 def _getentry(self):
1375 if self._entry is None:
1375 if self._entry is None:
1376 if isinstance(self._id, basestring):
1376 if isinstance(self._id, basestring):
1377 self._entry = grp.getgrnam(self._id)
1377 self._entry = grp.getgrnam(self._id)
1378 else:
1378 else:
1379 self._entry = grp.getgrgid(self._id)
1379 self._entry = grp.getgrgid(self._id)
1380 return self._entry
1380 return self._entry
1381
1381
1382 def getname(self):
1382 def getname(self):
1383 if isinstance(self._id, basestring):
1383 if isinstance(self._id, basestring):
1384 return self._id
1384 return self._id
1385 else:
1385 else:
1386 return self._getentry().gr_name
1386 return self._getentry().gr_name
1387 name = property(getname, None, None, "Group name")
1387 name = property(getname, None, None, "Group name")
1388
1388
1389 def getpasswd(self):
1389 def getpasswd(self):
1390 return self._getentry().gr_passwd
1390 return self._getentry().gr_passwd
1391 passwd = property(getpasswd, None, None, "Password")
1391 passwd = property(getpasswd, None, None, "Password")
1392
1392
1393 def getgid(self):
1393 def getgid(self):
1394 if isinstance(self._id, basestring):
1394 if isinstance(self._id, basestring):
1395 return self._getentry().gr_gid
1395 return self._getentry().gr_gid
1396 else:
1396 else:
1397 return self._id
1397 return self._id
1398 gid = property(getgid, None, None, "Group id")
1398 gid = property(getgid, None, None, "Group id")
1399
1399
1400 def getmem(self):
1400 def getmem(self):
1401 return self._getentry().gr_mem
1401 return self._getentry().gr_mem
1402 mem = property(getmem, None, None, "Members")
1402 mem = property(getmem, None, None, "Members")
1403
1403
1404 def __xattrs__(self, mode="default"):
1404 def __xattrs__(self, mode="default"):
1405 return ("name", "passwd", "gid", "mem")
1405 return ("name", "passwd", "gid", "mem")
1406
1406
1407 def __xrepr__(self, mode="default"):
1407 def __xrepr__(self, mode="default"):
1408 if mode == "header" or mode == "footer" or mode == "cell":
1408 if mode == "header" or mode == "footer" or mode == "cell":
1409 yield (astyle.style_default, "group ")
1409 yield (astyle.style_default, "group ")
1410 try:
1410 try:
1411 yield (astyle.style_default, self.name)
1411 yield (astyle.style_default, self.name)
1412 except KeyError:
1412 except KeyError:
1413 if isinstance(self._id, basestring):
1413 if isinstance(self._id, basestring):
1414 yield (astyle.style_default, self.name_id)
1414 yield (astyle.style_default, self.name_id)
1415 else:
1415 else:
1416 yield (astyle.style_type_number, str(self._id))
1416 yield (astyle.style_type_number, str(self._id))
1417 else:
1417 else:
1418 yield (astyle.style_default, repr(self))
1418 yield (astyle.style_default, repr(self))
1419
1419
1420 def __iter__(self):
1420 def __iter__(self):
1421 for member in self.mem:
1421 for member in self.mem:
1422 yield ipwdentry(member)
1422 yield ipwdentry(member)
1423
1423
1424 def __repr__(self):
1424 def __repr__(self):
1425 return "%s.%s(%r)" % \
1425 return "%s.%s(%r)" % \
1426 (self.__class__.__module__, self.__class__.__name__, self._id)
1426 (self.__class__.__module__, self.__class__.__name__, self._id)
1427
1427
1428
1428
1429 class igrp(Table):
1429 class igrp(Table):
1430 """
1430 """
1431 This ``Table`` lists all entries in the Unix group database.
1431 This ``Table`` lists all entries in the Unix group database.
1432 """
1432 """
1433 def __iter__(self):
1433 def __iter__(self):
1434 for entry in grp.getgrall():
1434 for entry in grp.getgrall():
1435 yield igrpentry(entry.gr_name)
1435 yield igrpentry(entry.gr_name)
1436
1436
1437 def __xrepr__(self, mode="default"):
1437 def __xrepr__(self, mode="default"):
1438 if mode == "header" or mode == "footer":
1438 if mode == "header" or mode == "footer":
1439 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1439 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1440 else:
1440 else:
1441 yield (astyle.style_default, repr(self))
1441 yield (astyle.style_default, repr(self))
1442
1442
1443
1443
1444 class Fields(object):
1444 class Fields(object):
1445 def __init__(self, fieldnames, **fields):
1445 def __init__(self, fieldnames, **fields):
1446 self.__fieldnames = [upgradexattr(fieldname) for fieldname in fieldnames]
1446 self.__fieldnames = [upgradexattr(fieldname) for fieldname in fieldnames]
1447 for (key, value) in fields.iteritems():
1447 for (key, value) in fields.iteritems():
1448 setattr(self, key, value)
1448 setattr(self, key, value)
1449
1449
1450 def __xattrs__(self, mode="default"):
1450 def __xattrs__(self, mode="default"):
1451 return self.__fieldnames
1451 return self.__fieldnames
1452
1452
1453 def __xrepr__(self, mode="default"):
1453 def __xrepr__(self, mode="default"):
1454 yield (-1, False)
1454 yield (-1, False)
1455 if mode == "header" or mode == "cell":
1455 if mode == "header" or mode == "cell":
1456 yield (astyle.style_default, self.__class__.__name__)
1456 yield (astyle.style_default, self.__class__.__name__)
1457 yield (astyle.style_default, "(")
1457 yield (astyle.style_default, "(")
1458 for (i, f) in enumerate(self.__fieldnames):
1458 for (i, f) in enumerate(self.__fieldnames):
1459 if i:
1459 if i:
1460 yield (astyle.style_default, ", ")
1460 yield (astyle.style_default, ", ")
1461 yield (astyle.style_default, f.name())
1461 yield (astyle.style_default, f.name())
1462 yield (astyle.style_default, "=")
1462 yield (astyle.style_default, "=")
1463 for part in xrepr(getattr(self, f), "default"):
1463 for part in xrepr(getattr(self, f), "default"):
1464 yield part
1464 yield part
1465 yield (astyle.style_default, ")")
1465 yield (astyle.style_default, ")")
1466 elif mode == "footer":
1466 elif mode == "footer":
1467 yield (astyle.style_default, self.__class__.__name__)
1467 yield (astyle.style_default, self.__class__.__name__)
1468 yield (astyle.style_default, "(")
1468 yield (astyle.style_default, "(")
1469 for (i, f) in enumerate(self.__fieldnames):
1469 for (i, f) in enumerate(self.__fieldnames):
1470 if i:
1470 if i:
1471 yield (astyle.style_default, ", ")
1471 yield (astyle.style_default, ", ")
1472 yield (astyle.style_default, f.name())
1472 yield (astyle.style_default, f.name())
1473 yield (astyle.style_default, ")")
1473 yield (astyle.style_default, ")")
1474 else:
1474 else:
1475 yield (astyle.style_default, repr(self))
1475 yield (astyle.style_default, repr(self))
1476
1476
1477
1477
1478 class FieldTable(Table, list):
1478 class FieldTable(Table, list):
1479 def __init__(self, *fields):
1479 def __init__(self, *fields):
1480 Table.__init__(self)
1480 Table.__init__(self)
1481 list.__init__(self)
1481 list.__init__(self)
1482 self.fields = fields
1482 self.fields = fields
1483
1483
1484 def add(self, **fields):
1484 def add(self, **fields):
1485 self.append(Fields(self.fields, **fields))
1485 self.append(Fields(self.fields, **fields))
1486
1486
1487 def __xrepr__(self, mode="default"):
1487 def __xrepr__(self, mode="default"):
1488 yield (-1, False)
1488 yield (-1, False)
1489 if mode == "header" or mode == "footer":
1489 if mode == "header" or mode == "footer":
1490 yield (astyle.style_default, self.__class__.__name__)
1490 yield (astyle.style_default, self.__class__.__name__)
1491 yield (astyle.style_default, "(")
1491 yield (astyle.style_default, "(")
1492 for (i, f) in enumerate(self.__fieldnames):
1492 for (i, f) in enumerate(self.__fieldnames):
1493 if i:
1493 if i:
1494 yield (astyle.style_default, ", ")
1494 yield (astyle.style_default, ", ")
1495 yield (astyle.style_default, f)
1495 yield (astyle.style_default, f)
1496 yield (astyle.style_default, ")")
1496 yield (astyle.style_default, ")")
1497 else:
1497 else:
1498 yield (astyle.style_default, repr(self))
1498 yield (astyle.style_default, repr(self))
1499
1499
1500 def __repr__(self):
1500 def __repr__(self):
1501 return "<%s.%s object with fields=%r at 0x%x>" % \
1501 return "<%s.%s object with fields=%r at 0x%x>" % \
1502 (self.__class__.__module__, self.__class__.__name__,
1502 (self.__class__.__module__, self.__class__.__name__,
1503 ", ".join(map(repr, self.fields)), id(self))
1503 ", ".join(map(repr, self.fields)), id(self))
1504
1504
1505
1505
1506 class List(list):
1506 class List(list):
1507 def __xattrs__(self, mode="default"):
1507 def __xattrs__(self, mode="default"):
1508 return xrange(len(self))
1508 return xrange(len(self))
1509
1509
1510 def __xrepr__(self, mode="default"):
1510 def __xrepr__(self, mode="default"):
1511 yield (-1, False)
1511 yield (-1, False)
1512 if mode == "header" or mode == "cell" or mode == "footer" or mode == "default":
1512 if mode == "header" or mode == "cell" or mode == "footer" or mode == "default":
1513 yield (astyle.style_default, self.__class__.__name__)
1513 yield (astyle.style_default, self.__class__.__name__)
1514 yield (astyle.style_default, "(")
1514 yield (astyle.style_default, "(")
1515 for (i, item) in enumerate(self):
1515 for (i, item) in enumerate(self):
1516 if i:
1516 if i:
1517 yield (astyle.style_default, ", ")
1517 yield (astyle.style_default, ", ")
1518 for part in xrepr(item, "default"):
1518 for part in xrepr(item, "default"):
1519 yield part
1519 yield part
1520 yield (astyle.style_default, ")")
1520 yield (astyle.style_default, ")")
1521 else:
1521 else:
1522 yield (astyle.style_default, repr(self))
1522 yield (astyle.style_default, repr(self))
1523
1523
1524
1524
1525 class ienv(Table):
1525 class ienv(Table):
1526 """
1526 """
1527 List environment variables.
1527 List environment variables.
1528
1528
1529 Example:
1529 Example:
1530
1530
1531 >>> ienv
1531 >>> ienv
1532 """
1532 """
1533
1533
1534 def __iter__(self):
1534 def __iter__(self):
1535 fields = ("key", "value")
1535 fields = ("key", "value")
1536 for (key, value) in os.environ.iteritems():
1536 for (key, value) in os.environ.iteritems():
1537 yield Fields(fields, key=key, value=value)
1537 yield Fields(fields, key=key, value=value)
1538
1538
1539 def __xrepr__(self, mode="default"):
1539 def __xrepr__(self, mode="default"):
1540 if mode == "header" or mode == "cell":
1540 if mode == "header" or mode == "cell":
1541 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1541 yield (astyle.style_default, "%s()" % self.__class__.__name__)
1542 else:
1542 else:
1543 yield (astyle.style_default, repr(self))
1543 yield (astyle.style_default, repr(self))
1544
1544
1545
1545
1546 class icsv(Pipe):
1546 class icsv(Pipe):
1547 """
1547 """
1548 This ``Pipe`` lists turn the input (with must be a pipe outputting lines
1548 This ``Pipe`` lists turn the input (with must be a pipe outputting lines
1549 or an ``ifile``) into lines of CVS columns.
1549 or an ``ifile``) into lines of CVS columns.
1550 """
1550 """
1551 def __init__(self, **csvargs):
1551 def __init__(self, **csvargs):
1552 """
1552 """
1553 Create an ``icsv`` object. ``cvsargs`` will be passed through as
1553 Create an ``icsv`` object. ``cvsargs`` will be passed through as
1554 keyword arguments to ``cvs.reader()``.
1554 keyword arguments to ``cvs.reader()``.
1555 """
1555 """
1556 self.csvargs = csvargs
1556 self.csvargs = csvargs
1557
1557
1558 def __iter__(self):
1558 def __iter__(self):
1559 input = self.input
1559 input = self.input
1560 if isinstance(input, ifile):
1560 if isinstance(input, ifile):
1561 input = input.open("rb")
1561 input = input.open("rb")
1562 reader = csv.reader(input, **self.csvargs)
1562 reader = csv.reader(input, **self.csvargs)
1563 for line in reader:
1563 for line in reader:
1564 yield List(line)
1564 yield List(line)
1565
1565
1566 def __xrepr__(self, mode="default"):
1566 def __xrepr__(self, mode="default"):
1567 yield (-1, False)
1567 yield (-1, False)
1568 if mode == "header" or mode == "footer":
1568 if mode == "header" or mode == "footer":
1569 input = getattr(self, "input", None)
1569 input = getattr(self, "input", None)
1570 if input is not None:
1570 if input is not None:
1571 for part in xrepr(input, mode):
1571 for part in xrepr(input, mode):
1572 yield part
1572 yield part
1573 yield (astyle.style_default, " | ")
1573 yield (astyle.style_default, " | ")
1574 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1574 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1575 for (i, (name, value)) in enumerate(self.csvargs.iteritems()):
1575 for (i, (name, value)) in enumerate(self.csvargs.iteritems()):
1576 if i:
1576 if i:
1577 yield (astyle.style_default, ", ")
1577 yield (astyle.style_default, ", ")
1578 yield (astyle.style_default, name)
1578 yield (astyle.style_default, name)
1579 yield (astyle.style_default, "=")
1579 yield (astyle.style_default, "=")
1580 for part in xrepr(value, "default"):
1580 for part in xrepr(value, "default"):
1581 yield part
1581 yield part
1582 yield (astyle.style_default, ")")
1582 yield (astyle.style_default, ")")
1583 else:
1583 else:
1584 yield (astyle.style_default, repr(self))
1584 yield (astyle.style_default, repr(self))
1585
1585
1586 def __repr__(self):
1586 def __repr__(self):
1587 args = ", ".join(["%s=%r" % item for item in self.csvargs.iteritems()])
1587 args = ", ".join(["%s=%r" % item for item in self.csvargs.iteritems()])
1588 return "<%s.%s %s at 0x%x>" % \
1588 return "<%s.%s %s at 0x%x>" % \
1589 (self.__class__.__module__, self.__class__.__name__, args, id(self))
1589 (self.__class__.__module__, self.__class__.__name__, args, id(self))
1590
1590
1591
1591
1592 class ix(Table):
1592 class ix(Table):
1593 """
1593 """
1594 Execute a system command and list its output as lines
1594 Execute a system command and list its output as lines
1595 (similar to ``os.popen()``).
1595 (similar to ``os.popen()``).
1596
1596
1597 Examples:
1597 Examples:
1598
1598
1599 >>> ix("ps x")
1599 >>> ix("ps x")
1600 >>> ix("find .") | ifile
1600 >>> ix("find .") | ifile
1601 """
1601 """
1602 def __init__(self, cmd):
1602 def __init__(self, cmd):
1603 self.cmd = cmd
1603 self.cmd = cmd
1604 self._pipeout = None
1604 self._pipeout = None
1605
1605
1606 def __iter__(self):
1606 def __iter__(self):
1607 (_pipein, self._pipeout) = os.popen4(self.cmd)
1607 (_pipein, self._pipeout) = os.popen4(self.cmd)
1608 _pipein.close()
1608 _pipein.close()
1609 for l in self._pipeout:
1609 for l in self._pipeout:
1610 yield l.rstrip("\r\n")
1610 yield l.rstrip("\r\n")
1611 self._pipeout.close()
1611 self._pipeout.close()
1612 self._pipeout = None
1612 self._pipeout = None
1613
1613
1614 def __del__(self):
1614 def __del__(self):
1615 if self._pipeout is not None and not self._pipeout.closed:
1615 if self._pipeout is not None and not self._pipeout.closed:
1616 self._pipeout.close()
1616 self._pipeout.close()
1617 self._pipeout = None
1617 self._pipeout = None
1618
1618
1619 def __xrepr__(self, mode="default"):
1619 def __xrepr__(self, mode="default"):
1620 if mode == "header" or mode == "footer":
1620 if mode == "header" or mode == "footer":
1621 yield (astyle.style_default,
1621 yield (astyle.style_default,
1622 "%s(%r)" % (self.__class__.__name__, self.cmd))
1622 "%s(%r)" % (self.__class__.__name__, self.cmd))
1623 else:
1623 else:
1624 yield (astyle.style_default, repr(self))
1624 yield (astyle.style_default, repr(self))
1625
1625
1626 def __repr__(self):
1626 def __repr__(self):
1627 return "%s.%s(%r)" % \
1627 return "%s.%s(%r)" % \
1628 (self.__class__.__module__, self.__class__.__name__, self.cmd)
1628 (self.__class__.__module__, self.__class__.__name__, self.cmd)
1629
1629
1630
1630
1631 class ifilter(Pipe):
1631 class ifilter(Pipe):
1632 """
1632 """
1633 Filter an input pipe. Only objects where an expression evaluates to true
1633 Filter an input pipe. Only objects where an expression evaluates to true
1634 (and doesn't raise an exception) are listed.
1634 (and doesn't raise an exception) are listed.
1635
1635
1636 Examples:
1636 Examples:
1637
1637
1638 >>> ils | ifilter("_.isfile() and size>1000")
1638 >>> ils | ifilter("_.isfile() and size>1000")
1639 >>> igrp | ifilter("len(mem)")
1639 >>> igrp | ifilter("len(mem)")
1640 >>> sys.modules | ifilter(lambda _:_.value is not None)
1640 >>> sys.modules | ifilter(lambda _:_.value is not None)
1641 """
1641 """
1642
1642
1643 def __init__(self, expr, globals=None, errors="raiseifallfail"):
1643 def __init__(self, expr, globals=None, errors="raiseifallfail"):
1644 """
1644 """
1645 Create an ``ifilter`` object. ``expr`` can be a callable or a string
1645 Create an ``ifilter`` object. ``expr`` can be a callable or a string
1646 containing an expression. ``globals`` will be used as the global
1646 containing an expression. ``globals`` will be used as the global
1647 namespace for calling string expressions (defaulting to IPython's
1647 namespace for calling string expressions (defaulting to IPython's
1648 user namespace). ``errors`` specifies how exception during evaluation
1648 user namespace). ``errors`` specifies how exception during evaluation
1649 of ``expr`` are handled:
1649 of ``expr`` are handled:
1650
1650
1651 * ``drop``: drop all items that have errors;
1651 * ``drop``: drop all items that have errors;
1652
1652
1653 * ``keep``: keep all items that have errors;
1653 * ``keep``: keep all items that have errors;
1654
1654
1655 * ``keeperror``: keep the exception of all items that have errors;
1655 * ``keeperror``: keep the exception of all items that have errors;
1656
1656
1657 * ``raise``: raise the exception;
1657 * ``raise``: raise the exception;
1658
1658
1659 * ``raiseifallfail``: raise the first exception if all items have errors;
1659 * ``raiseifallfail``: raise the first exception if all items have errors;
1660 otherwise drop those with errors (this is the default).
1660 otherwise drop those with errors (this is the default).
1661 """
1661 """
1662 self.expr = expr
1662 self.expr = expr
1663 self.globals = globals
1663 self.globals = globals
1664 self.errors = errors
1664 self.errors = errors
1665
1665
1666 def __iter__(self):
1666 def __iter__(self):
1667 if callable(self.expr):
1667 if callable(self.expr):
1668 test = self.expr
1668 test = self.expr
1669 else:
1669 else:
1670 g = getglobals(self.globals)
1670 g = getglobals(self.globals)
1671 expr = compile(self.expr, "ipipe-expression", "eval")
1671 expr = compile(self.expr, "ipipe-expression", "eval")
1672 def test(item):
1672 def test(item):
1673 return eval(expr, g, AttrNamespace(item))
1673 return eval(expr, g, AttrNamespace(item))
1674
1674
1675 ok = 0
1675 ok = 0
1676 exc_info = None
1676 exc_info = None
1677 for item in xiter(self.input):
1677 for item in xiter(self.input):
1678 try:
1678 try:
1679 if test(item):
1679 if test(item):
1680 yield item
1680 yield item
1681 ok += 1
1681 ok += 1
1682 except (KeyboardInterrupt, SystemExit):
1682 except (KeyboardInterrupt, SystemExit):
1683 raise
1683 raise
1684 except Exception, exc:
1684 except Exception, exc:
1685 if self.errors == "drop":
1685 if self.errors == "drop":
1686 pass # Ignore errors
1686 pass # Ignore errors
1687 elif self.errors == "keep":
1687 elif self.errors == "keep":
1688 yield item
1688 yield item
1689 elif self.errors == "keeperror":
1689 elif self.errors == "keeperror":
1690 yield exc
1690 yield exc
1691 elif self.errors == "raise":
1691 elif self.errors == "raise":
1692 raise
1692 raise
1693 elif self.errors == "raiseifallfail":
1693 elif self.errors == "raiseifallfail":
1694 if exc_info is None:
1694 if exc_info is None:
1695 exc_info = sys.exc_info()
1695 exc_info = sys.exc_info()
1696 if not ok and exc_info is not None:
1696 if not ok and exc_info is not None:
1697 raise exc_info[0], exc_info[1], exc_info[2]
1697 raise exc_info[0], exc_info[1], exc_info[2]
1698
1698
1699 def __xrepr__(self, mode="default"):
1699 def __xrepr__(self, mode="default"):
1700 if mode == "header" or mode == "footer":
1700 if mode == "header" or mode == "footer":
1701 input = getattr(self, "input", None)
1701 input = getattr(self, "input", None)
1702 if input is not None:
1702 if input is not None:
1703 for part in xrepr(input, mode):
1703 for part in xrepr(input, mode):
1704 yield part
1704 yield part
1705 yield (astyle.style_default, " | ")
1705 yield (astyle.style_default, " | ")
1706 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1706 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1707 for part in xrepr(self.expr, "default"):
1707 for part in xrepr(self.expr, "default"):
1708 yield part
1708 yield part
1709 yield (astyle.style_default, ")")
1709 yield (astyle.style_default, ")")
1710 else:
1710 else:
1711 yield (astyle.style_default, repr(self))
1711 yield (astyle.style_default, repr(self))
1712
1712
1713 def __repr__(self):
1713 def __repr__(self):
1714 return "<%s.%s expr=%r at 0x%x>" % \
1714 return "<%s.%s expr=%r at 0x%x>" % \
1715 (self.__class__.__module__, self.__class__.__name__,
1715 (self.__class__.__module__, self.__class__.__name__,
1716 self.expr, id(self))
1716 self.expr, id(self))
1717
1717
1718
1718
1719 class ieval(Pipe):
1719 class ieval(Pipe):
1720 """
1720 """
1721 Evaluate an expression for each object in the input pipe.
1721 Evaluate an expression for each object in the input pipe.
1722
1722
1723 Examples:
1723 Examples:
1724
1724
1725 >>> ils | ieval("_.abspath()")
1725 >>> ils | ieval("_.abspath()")
1726 >>> sys.path | ieval(ifile)
1726 >>> sys.path | ieval(ifile)
1727 """
1727 """
1728
1728
1729 def __init__(self, expr, globals=None, errors="raiseifallfail"):
1729 def __init__(self, expr, globals=None, errors="raiseifallfail"):
1730 """
1730 """
1731 Create an ``ieval`` object. ``expr`` can be a callable or a string
1731 Create an ``ieval`` object. ``expr`` can be a callable or a string
1732 containing an expression. For the meaning of ``globals`` and
1732 containing an expression. For the meaning of ``globals`` and
1733 ``errors`` see ``ifilter``.
1733 ``errors`` see ``ifilter``.
1734 """
1734 """
1735 self.expr = expr
1735 self.expr = expr
1736 self.globals = globals
1736 self.globals = globals
1737 self.errors = errors
1737 self.errors = errors
1738
1738
1739 def __iter__(self):
1739 def __iter__(self):
1740 if callable(self.expr):
1740 if callable(self.expr):
1741 do = self.expr
1741 do = self.expr
1742 else:
1742 else:
1743 g = getglobals(self.globals)
1743 g = getglobals(self.globals)
1744 expr = compile(self.expr, "ipipe-expression", "eval")
1744 expr = compile(self.expr, "ipipe-expression", "eval")
1745 def do(item):
1745 def do(item):
1746 return eval(expr, g, AttrNamespace(item))
1746 return eval(expr, g, AttrNamespace(item))
1747
1747
1748 ok = 0
1748 ok = 0
1749 exc_info = None
1749 exc_info = None
1750 for item in xiter(self.input):
1750 for item in xiter(self.input):
1751 try:
1751 try:
1752 yield do(item)
1752 yield do(item)
1753 except (KeyboardInterrupt, SystemExit):
1753 except (KeyboardInterrupt, SystemExit):
1754 raise
1754 raise
1755 except Exception, exc:
1755 except Exception, exc:
1756 if self.errors == "drop":
1756 if self.errors == "drop":
1757 pass # Ignore errors
1757 pass # Ignore errors
1758 elif self.errors == "keep":
1758 elif self.errors == "keep":
1759 yield item
1759 yield item
1760 elif self.errors == "keeperror":
1760 elif self.errors == "keeperror":
1761 yield exc
1761 yield exc
1762 elif self.errors == "raise":
1762 elif self.errors == "raise":
1763 raise
1763 raise
1764 elif self.errors == "raiseifallfail":
1764 elif self.errors == "raiseifallfail":
1765 if exc_info is None:
1765 if exc_info is None:
1766 exc_info = sys.exc_info()
1766 exc_info = sys.exc_info()
1767 if not ok and exc_info is not None:
1767 if not ok and exc_info is not None:
1768 raise exc_info[0], exc_info[1], exc_info[2]
1768 raise exc_info[0], exc_info[1], exc_info[2]
1769
1769
1770 def __xrepr__(self, mode="default"):
1770 def __xrepr__(self, mode="default"):
1771 if mode == "header" or mode == "footer":
1771 if mode == "header" or mode == "footer":
1772 input = getattr(self, "input", None)
1772 input = getattr(self, "input", None)
1773 if input is not None:
1773 if input is not None:
1774 for part in xrepr(input, mode):
1774 for part in xrepr(input, mode):
1775 yield part
1775 yield part
1776 yield (astyle.style_default, " | ")
1776 yield (astyle.style_default, " | ")
1777 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1777 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1778 for part in xrepr(self.expr, "default"):
1778 for part in xrepr(self.expr, "default"):
1779 yield part
1779 yield part
1780 yield (astyle.style_default, ")")
1780 yield (astyle.style_default, ")")
1781 else:
1781 else:
1782 yield (astyle.style_default, repr(self))
1782 yield (astyle.style_default, repr(self))
1783
1783
1784 def __repr__(self):
1784 def __repr__(self):
1785 return "<%s.%s expr=%r at 0x%x>" % \
1785 return "<%s.%s expr=%r at 0x%x>" % \
1786 (self.__class__.__module__, self.__class__.__name__,
1786 (self.__class__.__module__, self.__class__.__name__,
1787 self.expr, id(self))
1787 self.expr, id(self))
1788
1788
1789
1789
1790 class ienum(Pipe):
1790 class ienum(Pipe):
1791 """
1791 """
1792 Enumerate the input pipe (i.e. wrap each input object in an object
1792 Enumerate the input pipe (i.e. wrap each input object in an object
1793 with ``index`` and ``object`` attributes).
1793 with ``index`` and ``object`` attributes).
1794
1794
1795 Examples:
1795 Examples:
1796
1796
1797 >>> xrange(20) | ieval("_,_*_") | ienum | ifilter("index % 2 == 0") | ieval("object")
1797 >>> xrange(20) | ieval("_,_*_") | ienum | ifilter("index % 2 == 0") | ieval("object")
1798 """
1798 """
1799 def __iter__(self):
1799 def __iter__(self):
1800 fields = ("index", "object")
1800 fields = ("index", "object")
1801 for (index, object) in enumerate(xiter(self.input)):
1801 for (index, object) in enumerate(xiter(self.input)):
1802 yield Fields(fields, index=index, object=object)
1802 yield Fields(fields, index=index, object=object)
1803
1803
1804
1804
1805 class isort(Pipe):
1805 class isort(Pipe):
1806 """
1806 """
1807 Sorts the input pipe.
1807 Sorts the input pipe.
1808
1808
1809 Examples:
1809 Examples:
1810
1810
1811 >>> ils | isort("size")
1811 >>> ils | isort("size")
1812 >>> ils | isort("_.isdir(), _.lower()", reverse=True)
1812 >>> ils | isort("_.isdir(), _.lower()", reverse=True)
1813 """
1813 """
1814
1814
1815 def __init__(self, key=None, globals=None, reverse=False):
1815 def __init__(self, key=None, globals=None, reverse=False):
1816 """
1816 """
1817 Create an ``isort`` object. ``key`` can be a callable or a string
1817 Create an ``isort`` object. ``key`` can be a callable or a string
1818 containing an expression (or ``None`` in which case the items
1818 containing an expression (or ``None`` in which case the items
1819 themselves will be sorted). If ``reverse`` is true the sort order
1819 themselves will be sorted). If ``reverse`` is true the sort order
1820 will be reversed. For the meaning of ``globals`` see ``ifilter``.
1820 will be reversed. For the meaning of ``globals`` see ``ifilter``.
1821 """
1821 """
1822 self.key = key
1822 self.key = key
1823 self.globals = globals
1823 self.globals = globals
1824 self.reverse = reverse
1824 self.reverse = reverse
1825
1825
1826 def __iter__(self):
1826 def __iter__(self):
1827 if self.key is None:
1827 if self.key is None:
1828 items = sorted(xiter(self.input), reverse=self.reverse)
1828 items = sorted(xiter(self.input), reverse=self.reverse)
1829 elif callable(self.key):
1829 elif callable(self.key):
1830 items = sorted(xiter(self.input), key=self.key, reverse=self.reverse)
1830 items = sorted(xiter(self.input), key=self.key, reverse=self.reverse)
1831 else:
1831 else:
1832 g = getglobals(self.globals)
1832 g = getglobals(self.globals)
1833 key = compile(self.key, "ipipe-expression", "eval")
1833 key = compile(self.key, "ipipe-expression", "eval")
1834 def realkey(item):
1834 def realkey(item):
1835 return eval(key, g, AttrNamespace(item))
1835 return eval(key, g, AttrNamespace(item))
1836 items = sorted(xiter(self.input), key=realkey, reverse=self.reverse)
1836 items = sorted(xiter(self.input), key=realkey, reverse=self.reverse)
1837 for item in items:
1837 for item in items:
1838 yield item
1838 yield item
1839
1839
1840 def __xrepr__(self, mode="default"):
1840 def __xrepr__(self, mode="default"):
1841 if mode == "header" or mode == "footer":
1841 if mode == "header" or mode == "footer":
1842 input = getattr(self, "input", None)
1842 input = getattr(self, "input", None)
1843 if input is not None:
1843 if input is not None:
1844 for part in xrepr(input, mode):
1844 for part in xrepr(input, mode):
1845 yield part
1845 yield part
1846 yield (astyle.style_default, " | ")
1846 yield (astyle.style_default, " | ")
1847 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1847 yield (astyle.style_default, "%s(" % self.__class__.__name__)
1848 for part in xrepr(self.key, "default"):
1848 for part in xrepr(self.key, "default"):
1849 yield part
1849 yield part
1850 if self.reverse:
1850 if self.reverse:
1851 yield (astyle.style_default, ", ")
1851 yield (astyle.style_default, ", ")
1852 for part in xrepr(True, "default"):
1852 for part in xrepr(True, "default"):
1853 yield part
1853 yield part
1854 yield (astyle.style_default, ")")
1854 yield (astyle.style_default, ")")
1855 else:
1855 else:
1856 yield (astyle.style_default, repr(self))
1856 yield (astyle.style_default, repr(self))
1857
1857
1858 def __repr__(self):
1858 def __repr__(self):
1859 return "<%s.%s key=%r reverse=%r at 0x%x>" % \
1859 return "<%s.%s key=%r reverse=%r at 0x%x>" % \
1860 (self.__class__.__module__, self.__class__.__name__,
1860 (self.__class__.__module__, self.__class__.__name__,
1861 self.key, self.reverse, id(self))
1861 self.key, self.reverse, id(self))
1862
1862
1863
1863
1864 tab = 3 # for expandtabs()
1864 tab = 3 # for expandtabs()
1865
1865
1866 def _format(field):
1866 def _format(field):
1867 if isinstance(field, str):
1867 if isinstance(field, str):
1868 text = repr(field.expandtabs(tab))[1:-1]
1868 text = repr(field.expandtabs(tab))[1:-1]
1869 elif isinstance(field, unicode):
1869 elif isinstance(field, unicode):
1870 text = repr(field.expandtabs(tab))[2:-1]
1870 text = repr(field.expandtabs(tab))[2:-1]
1871 elif isinstance(field, datetime.datetime):
1871 elif isinstance(field, datetime.datetime):
1872 # Don't use strftime() here, as this requires year >= 1900
1872 # Don't use strftime() here, as this requires year >= 1900
1873 text = "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
1873 text = "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \
1874 (field.year, field.month, field.day,
1874 (field.year, field.month, field.day,
1875 field.hour, field.minute, field.second, field.microsecond)
1875 field.hour, field.minute, field.second, field.microsecond)
1876 elif isinstance(field, datetime.date):
1876 elif isinstance(field, datetime.date):
1877 text = "%04d-%02d-%02d" % (field.year, field.month, field.day)
1877 text = "%04d-%02d-%02d" % (field.year, field.month, field.day)
1878 else:
1878 else:
1879 text = repr(field)
1879 text = repr(field)
1880 return text
1880 return text
1881
1881
1882
1882
1883 class Display(object):
1883 class Display(object):
1884 class __metaclass__(type):
1884 class __metaclass__(type):
1885 def __ror__(self, input):
1885 def __ror__(self, input):
1886 return input | self()
1886 return input | self()
1887
1887
1888 def __ror__(self, input):
1888 def __ror__(self, input):
1889 self.input = input
1889 self.input = input
1890 return self
1890 return self
1891
1891
1892 def display(self):
1892 def display(self):
1893 pass
1893 pass
1894
1894
1895
1895
1896 class iless(Display):
1896 class iless(Display):
1897 cmd = "less --quit-if-one-screen --LONG-PROMPT --LINE-NUMBERS --chop-long-lines --shift=8 --RAW-CONTROL-CHARS"
1897 cmd = "less --quit-if-one-screen --LONG-PROMPT --LINE-NUMBERS --chop-long-lines --shift=8 --RAW-CONTROL-CHARS"
1898
1898
1899 def display(self):
1899 def display(self):
1900 try:
1900 try:
1901 pager = os.popen(self.cmd, "w")
1901 pager = os.popen(self.cmd, "w")
1902 try:
1902 try:
1903 for item in xiter(self.input):
1903 for item in xiter(self.input):
1904 first = False
1904 first = False
1905 for attr in xattrs(item, "default"):
1905 for attr in xattrs(item, "default"):
1906 if first:
1906 if first:
1907 first = False
1907 first = False
1908 else:
1908 else:
1909 pager.write(" ")
1909 pager.write(" ")
1910 attr = upgradexattr(attr)
1910 attr = upgradexattr(attr)
1911 if not isinstance(attr, SelfDescriptor):
1911 if not isinstance(attr, SelfDescriptor):
1912 pager.write(attr.name())
1912 pager.write(attr.name())
1913 pager.write("=")
1913 pager.write("=")
1914 pager.write(str(attr.value(item)))
1914 pager.write(str(attr.value(item)))
1915 pager.write("\n")
1915 pager.write("\n")
1916 finally:
1916 finally:
1917 pager.close()
1917 pager.close()
1918 except Exception, exc:
1918 except Exception, exc:
1919 print "%s: %s" % (exc.__class__.__name__, str(exc))
1919 print "%s: %s" % (exc.__class__.__name__, str(exc))
1920
1920
1921
1921
1922 def xformat(value, mode, maxlength):
1922 def xformat(value, mode, maxlength):
1923 align = None
1923 align = None
1924 full = True
1924 full = True
1925 width = 0
1925 width = 0
1926 text = astyle.Text()
1926 text = astyle.Text()
1927 for (style, part) in xrepr(value, mode):
1927 for (style, part) in xrepr(value, mode):
1928 # only consider the first result
1928 # only consider the first result
1929 if align is None:
1929 if align is None:
1930 if isinstance(style, int):
1930 if isinstance(style, int):
1931 # (style, text) really is (alignment, stop)
1931 # (style, text) really is (alignment, stop)
1932 align = style
1932 align = style
1933 full = part
1933 full = part
1934 continue
1934 continue
1935 else:
1935 else:
1936 align = -1
1936 align = -1
1937 full = True
1937 full = True
1938 if not isinstance(style, int):
1938 if not isinstance(style, int):
1939 text.append((style, part))
1939 text.append((style, part))
1940 width += len(part)
1940 width += len(part)
1941 if width >= maxlength and not full:
1941 if width >= maxlength and not full:
1942 text.append((astyle.style_ellisis, "..."))
1942 text.append((astyle.style_ellisis, "..."))
1943 width += 3
1943 width += 3
1944 break
1944 break
1945 if align is None: # default to left alignment
1945 if align is None: # default to left alignment
1946 align = -1
1946 align = -1
1947 return (align, width, text)
1947 return (align, width, text)
1948
1948
1949
1949
1950 class idump(Display):
1950 class idump(Display):
1951 # The approximate maximum length of a column entry
1951 # The approximate maximum length of a column entry
1952 maxattrlength = 200
1952 maxattrlength = 200
1953
1953
1954 # Style for column names
1954 # Style for column names
1955 style_header = astyle.Style.fromstr("white:black:bold")
1955 style_header = astyle.Style.fromstr("white:black:bold")
1956
1956
1957 def __init__(self, *attrs):
1957 def __init__(self, *attrs):
1958 self.attrs = [upgradexattr(attr) for attr in attrs]
1958 self.attrs = [upgradexattr(attr) for attr in attrs]
1959 self.headerpadchar = " "
1959 self.headerpadchar = " "
1960 self.headersepchar = "|"
1960 self.headersepchar = "|"
1961 self.datapadchar = " "
1961 self.datapadchar = " "
1962 self.datasepchar = "|"
1962 self.datasepchar = "|"
1963
1963
1964 def display(self):
1964 def display(self):
1965 stream = genutils.Term.cout
1965 stream = genutils.Term.cout
1966 allattrs = []
1966 allattrs = []
1967 attrset = set()
1967 attrset = set()
1968 colwidths = {}
1968 colwidths = {}
1969 rows = []
1969 rows = []
1970 for item in xiter(self.input):
1970 for item in xiter(self.input):
1971 row = {}
1971 row = {}
1972 attrs = self.attrs
1972 attrs = self.attrs
1973 if not attrs:
1973 if not attrs:
1974 attrs = xattrs(item, "default")
1974 attrs = xattrs(item, "default")
1975 for attr in attrs:
1975 for attr in attrs:
1976 if attr not in attrset:
1976 if attr not in attrset:
1977 allattrs.append(attr)
1977 allattrs.append(attr)
1978 attrset.add(attr)
1978 attrset.add(attr)
1979 colwidths[attr] = len(attr.name())
1979 colwidths[attr] = len(attr.name())
1980 try:
1980 try:
1981 value = attr.value(item)
1981 value = attr.value(item)
1982 except (KeyboardInterrupt, SystemExit):
1982 except (KeyboardInterrupt, SystemExit):
1983 raise
1983 raise
1984 except Exception, exc:
1984 except Exception, exc:
1985 value = exc
1985 value = exc
1986 (align, width, text) = xformat(value, "cell", self.maxattrlength)
1986 (align, width, text) = xformat(value, "cell", self.maxattrlength)
1987 colwidths[attr] = max(colwidths[attr], width)
1987 colwidths[attr] = max(colwidths[attr], width)
1988 # remember alignment, length and colored parts
1988 # remember alignment, length and colored parts
1989 row[attr] = (align, width, text)
1989 row[attr] = (align, width, text)
1990 rows.append(row)
1990 rows.append(row)
1991
1991
1992 stream.write("\n")
1992 stream.write("\n")
1993 for (i, attr) in enumerate(allattrs):
1993 for (i, attr) in enumerate(allattrs):
1994 attrname = attr.name()
1994 attrname = attr.name()
1995 self.style_header(attrname).write(stream)
1995 self.style_header(attrname).write(stream)
1996 spc = colwidths[attr] - len(attrname)
1996 spc = colwidths[attr] - len(attrname)
1997 if i < len(colwidths)-1:
1997 if i < len(colwidths)-1:
1998 stream.write(self.headerpadchar*spc)
1998 stream.write(self.headerpadchar*spc)
1999 stream.write(self.headersepchar)
1999 stream.write(self.headersepchar)
2000 stream.write("\n")
2000 stream.write("\n")
2001
2001
2002 for row in rows:
2002 for row in rows:
2003 for (i, attr) in enumerate(allattrs):
2003 for (i, attr) in enumerate(allattrs):
2004 (align, width, text) = row[attr]
2004 (align, width, text) = row[attr]
2005 spc = colwidths[attr] - width
2005 spc = colwidths[attr] - width
2006 if align == -1:
2006 if align == -1:
2007 text.write(stream)
2007 text.write(stream)
2008 if i < len(colwidths)-1:
2008 if i < len(colwidths)-1:
2009 stream.write(self.datapadchar*spc)
2009 stream.write(self.datapadchar*spc)
2010 elif align == 0:
2010 elif align == 0:
2011 spc = colwidths[attr] - width
2011 spc = colwidths[attr] - width
2012 spc1 = spc//2
2012 spc1 = spc//2
2013 spc2 = spc-spc1
2013 spc2 = spc-spc1
2014 stream.write(self.datapadchar*spc1)
2014 stream.write(self.datapadchar*spc1)
2015 text.write(stream)
2015 text.write(stream)
2016 if i < len(colwidths)-1:
2016 if i < len(colwidths)-1:
2017 stream.write(self.datapadchar*spc2)
2017 stream.write(self.datapadchar*spc2)
2018 else:
2018 else:
2019 stream.write(self.datapadchar*spc)
2019 stream.write(self.datapadchar*spc)
2020 text.write(stream)
2020 text.write(stream)
2021 if i < len(colwidths)-1:
2021 if i < len(colwidths)-1:
2022 stream.write(self.datasepchar)
2022 stream.write(self.datasepchar)
2023 stream.write("\n")
2023 stream.write("\n")
2024
2024
2025
2025
2026 class AttributeDetail(Table):
2026 class AttributeDetail(Table):
2027 """
2027 """
2028 ``AttributeDetail`` objects are use for displaying a detailed list of object
2028 ``AttributeDetail`` objects are use for displaying a detailed list of object
2029 attributes.
2029 attributes.
2030 """
2030 """
2031 def __init__(self, object, descriptor):
2031 def __init__(self, object, descriptor):
2032 self.object = object
2032 self.object = object
2033 self.descriptor = descriptor
2033 self.descriptor = descriptor
2034
2034
2035 def __iter__(self):
2035 def __iter__(self):
2036 return self.descriptor.iter(self.object)
2036 return self.descriptor.iter(self.object)
2037
2037
2038 def name(self):
2038 def name(self):
2039 return self.descriptor.name()
2039 return self.descriptor.name()
2040
2040
2041 def attrtype(self):
2041 def attrtype(self):
2042 return self.descriptor.attrtype(self.object)
2042 return self.descriptor.attrtype(self.object)
2043
2043
2044 def valuetype(self):
2044 def valuetype(self):
2045 return self.descriptor.valuetype(self.object)
2045 return self.descriptor.valuetype(self.object)
2046
2046
2047 def doc(self):
2047 def doc(self):
2048 return self.descriptor.doc(self.object)
2048 return self.descriptor.doc(self.object)
2049
2049
2050 def shortdoc(self):
2050 def shortdoc(self):
2051 return self.descriptor.shortdoc(self.object)
2051 return self.descriptor.shortdoc(self.object)
2052
2052
2053 def value(self):
2053 def value(self):
2054 return self.descriptor.value(self.object)
2054 return self.descriptor.value(self.object)
2055
2055
2056 def __xattrs__(self, mode="default"):
2056 def __xattrs__(self, mode="default"):
2057 attrs = ("name()", "attrtype()", "valuetype()", "value()", "shortdoc()")
2057 attrs = ("name()", "attrtype()", "valuetype()", "value()", "shortdoc()")
2058 if mode == "detail":
2058 if mode == "detail":
2059 attrs += ("doc()",)
2059 attrs += ("doc()",)
2060 return attrs
2060 return attrs
2061
2061
2062 def __xrepr__(self, mode="default"):
2062 def __xrepr__(self, mode="default"):
2063 yield (-1, True)
2063 yield (-1, True)
2064 valuetype = self.valuetype()
2064 valuetype = self.valuetype()
2065 if valuetype is not noitem:
2065 if valuetype is not noitem:
2066 for part in xrepr(valuetype):
2066 for part in xrepr(valuetype):
2067 yield part
2067 yield part
2068 yield (astyle.style_default, " ")
2068 yield (astyle.style_default, " ")
2069 yield (astyle.style_default, self.attrtype())
2069 yield (astyle.style_default, self.attrtype())
2070 yield (astyle.style_default, " ")
2070 yield (astyle.style_default, " ")
2071 yield (astyle.style_default, self.name())
2071 yield (astyle.style_default, self.name())
2072 yield (astyle.style_default, " of ")
2072 yield (astyle.style_default, " of ")
2073 for part in xrepr(self.object):
2073 for part in xrepr(self.object):
2074 yield part
2074 yield part
2075
2075
2076
2076
2077 try:
2077 try:
2078 from ibrowse import ibrowse
2078 from ibrowse import ibrowse
2079 except ImportError:
2079 except ImportError:
2080 # No curses (probably Windows) => use ``idump`` as the default display.
2080 # No curses (probably Windows) => use ``idump`` as the default display.
2081 defaultdisplay = idump
2081 defaultdisplay = idump
2082 else:
2082 else:
2083 defaultdisplay = ibrowse
2083 defaultdisplay = ibrowse
2084 __all__.append("ibrowse")
2084 __all__.append("ibrowse")
2085
2085
2086
2086
2087 # If we're running under IPython, install an IPython displayhook that
2087 # If we're running under IPython, install an IPython displayhook that
2088 # returns the object from Display.display(), else install a displayhook
2088 # returns the object from Display.display(), else install a displayhook
2089 # directly as sys.displayhook
2089 # directly as sys.displayhook
2090 api = None
2090 api = None
2091 if ipapi is not None:
2091 if ipapi is not None:
2092 try:
2092 try:
2093 api = ipapi.get()
2093 api = ipapi.get()
2094 except AttributeError:
2094 except AttributeError:
2095 pass
2095 pass
2096
2096
2097 if api is not None:
2097 if api is not None:
2098 def displayhook(self, obj):
2098 def displayhook(self, obj):
2099 if isinstance(obj, type) and issubclass(obj, Table):
2099 if isinstance(obj, type) and issubclass(obj, Table):
2100 obj = obj()
2100 obj = obj()
2101 if isinstance(obj, Table):
2101 if isinstance(obj, Table):
2102 obj = obj | defaultdisplay
2102 obj = obj | defaultdisplay
2103 if isinstance(obj, Display):
2103 if isinstance(obj, Display):
2104 return obj.display()
2104 return obj.display()
2105 else:
2105 else:
2106 raise ipapi.TryNext
2106 raise ipapi.TryNext
2107 api.set_hook("result_display", displayhook)
2107 api.set_hook("result_display", displayhook)
2108 else:
2108 else:
2109 def installdisplayhook():
2109 def installdisplayhook():
2110 _originalhook = sys.displayhook
2110 _originalhook = sys.displayhook
2111 def displayhook(obj):
2111 def displayhook(obj):
2112 if isinstance(obj, type) and issubclass(obj, Table):
2112 if isinstance(obj, type) and issubclass(obj, Table):
2113 obj = obj()
2113 obj = obj()
2114 if isinstance(obj, Table):
2114 if isinstance(obj, Table):
2115 obj = obj | defaultdisplay
2115 obj = obj | defaultdisplay
2116 if isinstance(obj, Display):
2116 if isinstance(obj, Display):
2117 return obj.display()
2117 return obj.display()
2118 else:
2118 else:
2119 _originalhook(obj)
2119 _originalhook(obj)
2120 sys.displayhook = displayhook
2120 sys.displayhook = displayhook
2121 installdisplayhook()
2121 installdisplayhook()
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now