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