##// END OF EJS Templates
* IPython/Extensions/ipipe.py: Rename XAttr to AttributeDetail...
walter.doerwald -
Show More

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

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