##// END OF EJS Templates
Merging upstream changes.
Brian Granger -
r2086:1be452ef merge
parent child Browse files
Show More
@@ -1,1767 +1,1767 b''
1 # -*- coding: iso-8859-1 -*-
1 # -*- coding: iso-8859-1 -*-
2
2
3 import curses, fcntl, signal, struct, tty, textwrap, inspect
3 import curses, fcntl, signal, struct, tty, textwrap, inspect
4
4
5 from IPython.coreZ import ipapi
5 from IPython.core import ipapi
6
6
7 import astyle, ipipe
7 import astyle, ipipe
8
8
9
9
10 # Python 2.3 compatibility
10 # Python 2.3 compatibility
11 try:
11 try:
12 set
12 set
13 except NameError:
13 except NameError:
14 import sets
14 import sets
15 set = sets.Set
15 set = sets.Set
16
16
17 # Python 2.3 compatibility
17 # Python 2.3 compatibility
18 try:
18 try:
19 sorted
19 sorted
20 except NameError:
20 except NameError:
21 from ipipe import sorted
21 from ipipe import sorted
22
22
23
23
24 class UnassignedKeyError(Exception):
24 class UnassignedKeyError(Exception):
25 """
25 """
26 Exception that is used for reporting unassigned keys.
26 Exception that is used for reporting unassigned keys.
27 """
27 """
28
28
29
29
30 class UnknownCommandError(Exception):
30 class UnknownCommandError(Exception):
31 """
31 """
32 Exception that is used for reporting unknown commands (this should never
32 Exception that is used for reporting unknown commands (this should never
33 happen).
33 happen).
34 """
34 """
35
35
36
36
37 class CommandError(Exception):
37 class CommandError(Exception):
38 """
38 """
39 Exception that is used for reporting that a command can't be executed.
39 Exception that is used for reporting that a command can't be executed.
40 """
40 """
41
41
42
42
43 class Keymap(dict):
43 class Keymap(dict):
44 """
44 """
45 Stores mapping of keys to commands.
45 Stores mapping of keys to commands.
46 """
46 """
47 def __init__(self):
47 def __init__(self):
48 self._keymap = {}
48 self._keymap = {}
49
49
50 def __setitem__(self, key, command):
50 def __setitem__(self, key, command):
51 if isinstance(key, str):
51 if isinstance(key, str):
52 for c in key:
52 for c in key:
53 dict.__setitem__(self, ord(c), command)
53 dict.__setitem__(self, ord(c), command)
54 else:
54 else:
55 dict.__setitem__(self, key, command)
55 dict.__setitem__(self, key, command)
56
56
57 def __getitem__(self, key):
57 def __getitem__(self, key):
58 if isinstance(key, str):
58 if isinstance(key, str):
59 key = ord(key)
59 key = ord(key)
60 return dict.__getitem__(self, key)
60 return dict.__getitem__(self, key)
61
61
62 def __detitem__(self, key):
62 def __detitem__(self, key):
63 if isinstance(key, str):
63 if isinstance(key, str):
64 key = ord(key)
64 key = ord(key)
65 dict.__detitem__(self, key)
65 dict.__detitem__(self, key)
66
66
67 def register(self, command, *keys):
67 def register(self, command, *keys):
68 for key in keys:
68 for key in keys:
69 self[key] = command
69 self[key] = command
70
70
71 def get(self, key, default=None):
71 def get(self, key, default=None):
72 if isinstance(key, str):
72 if isinstance(key, str):
73 key = ord(key)
73 key = ord(key)
74 return dict.get(self, key, default)
74 return dict.get(self, key, default)
75
75
76 def findkey(self, command, default=ipipe.noitem):
76 def findkey(self, command, default=ipipe.noitem):
77 for (key, commandcandidate) in self.iteritems():
77 for (key, commandcandidate) in self.iteritems():
78 if commandcandidate == command:
78 if commandcandidate == command:
79 return key
79 return key
80 if default is ipipe.noitem:
80 if default is ipipe.noitem:
81 raise KeyError(command)
81 raise KeyError(command)
82 return default
82 return default
83
83
84
84
85 class _BrowserCachedItem(object):
85 class _BrowserCachedItem(object):
86 # This is used internally by ``ibrowse`` to store a item together with its
86 # This is used internally by ``ibrowse`` to store a item together with its
87 # marked status.
87 # marked status.
88 __slots__ = ("item", "marked")
88 __slots__ = ("item", "marked")
89
89
90 def __init__(self, item):
90 def __init__(self, item):
91 self.item = item
91 self.item = item
92 self.marked = False
92 self.marked = False
93
93
94
94
95 class _BrowserHelp(object):
95 class _BrowserHelp(object):
96 style_header = astyle.Style.fromstr("yellow:black:bold")
96 style_header = astyle.Style.fromstr("yellow:black:bold")
97 # This is used internally by ``ibrowse`` for displaying the help screen.
97 # This is used internally by ``ibrowse`` for displaying the help screen.
98 def __init__(self, browser):
98 def __init__(self, browser):
99 self.browser = browser
99 self.browser = browser
100
100
101 def __xrepr__(self, mode):
101 def __xrepr__(self, mode):
102 yield (-1, True)
102 yield (-1, True)
103 if mode == "header" or mode == "footer":
103 if mode == "header" or mode == "footer":
104 yield (astyle.style_default, "ibrowse help screen")
104 yield (astyle.style_default, "ibrowse help screen")
105 else:
105 else:
106 yield (astyle.style_default, repr(self))
106 yield (astyle.style_default, repr(self))
107
107
108 def __iter__(self):
108 def __iter__(self):
109 # Get reverse key mapping
109 # Get reverse key mapping
110 allkeys = {}
110 allkeys = {}
111 for (key, cmd) in self.browser.keymap.iteritems():
111 for (key, cmd) in self.browser.keymap.iteritems():
112 allkeys.setdefault(cmd, []).append(key)
112 allkeys.setdefault(cmd, []).append(key)
113
113
114 fields = ("key", "description")
114 fields = ("key", "description")
115
115
116 commands = []
116 commands = []
117 for name in dir(self.browser):
117 for name in dir(self.browser):
118 if name.startswith("cmd_"):
118 if name.startswith("cmd_"):
119 command = getattr(self.browser, name)
119 command = getattr(self.browser, name)
120 commands.append((inspect.getsourcelines(command)[-1], name[4:], command))
120 commands.append((inspect.getsourcelines(command)[-1], name[4:], command))
121 commands.sort()
121 commands.sort()
122 commands = [(c[1], c[2]) for c in commands]
122 commands = [(c[1], c[2]) for c in commands]
123 for (i, (name, command)) in enumerate(commands):
123 for (i, (name, command)) in enumerate(commands):
124 if i:
124 if i:
125 yield ipipe.Fields(fields, key="", description="")
125 yield ipipe.Fields(fields, key="", description="")
126
126
127 description = command.__doc__
127 description = command.__doc__
128 if description is None:
128 if description is None:
129 lines = []
129 lines = []
130 else:
130 else:
131 lines = [l.strip() for l in description.splitlines() if l.strip()]
131 lines = [l.strip() for l in description.splitlines() if l.strip()]
132 description = "\n".join(lines)
132 description = "\n".join(lines)
133 lines = textwrap.wrap(description, 60)
133 lines = textwrap.wrap(description, 60)
134 keys = allkeys.get(name, [])
134 keys = allkeys.get(name, [])
135
135
136 yield ipipe.Fields(fields, key="", description=astyle.Text((self.style_header, name)))
136 yield ipipe.Fields(fields, key="", description=astyle.Text((self.style_header, name)))
137 for i in xrange(max(len(keys), len(lines))):
137 for i in xrange(max(len(keys), len(lines))):
138 try:
138 try:
139 key = self.browser.keylabel(keys[i])
139 key = self.browser.keylabel(keys[i])
140 except IndexError:
140 except IndexError:
141 key = ""
141 key = ""
142 try:
142 try:
143 line = lines[i]
143 line = lines[i]
144 except IndexError:
144 except IndexError:
145 line = ""
145 line = ""
146 yield ipipe.Fields(fields, key=key, description=line)
146 yield ipipe.Fields(fields, key=key, description=line)
147
147
148
148
149 class _BrowserLevel(object):
149 class _BrowserLevel(object):
150 # This is used internally to store the state (iterator, fetch items,
150 # This is used internally to store the state (iterator, fetch items,
151 # position of cursor and screen, etc.) of one browser level
151 # position of cursor and screen, etc.) of one browser level
152 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
152 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
153 # a stack.
153 # a stack.
154 def __init__(self, browser, input, mainsizey, *attrs):
154 def __init__(self, browser, input, mainsizey, *attrs):
155 self.browser = browser
155 self.browser = browser
156 self.input = input
156 self.input = input
157 self.header = [x for x in ipipe.xrepr(input, "header") if not isinstance(x[0], int)]
157 self.header = [x for x in ipipe.xrepr(input, "header") if not isinstance(x[0], int)]
158 # iterator for the input
158 # iterator for the input
159 self.iterator = ipipe.xiter(input)
159 self.iterator = ipipe.xiter(input)
160
160
161 # is the iterator exhausted?
161 # is the iterator exhausted?
162 self.exhausted = False
162 self.exhausted = False
163
163
164 # attributes to be display (autodetected if empty)
164 # attributes to be display (autodetected if empty)
165 self.attrs = attrs
165 self.attrs = attrs
166
166
167 # fetched items (+ marked flag)
167 # fetched items (+ marked flag)
168 self.items = ipipe.deque()
168 self.items = ipipe.deque()
169
169
170 # Number of marked objects
170 # Number of marked objects
171 self.marked = 0
171 self.marked = 0
172
172
173 # Vertical cursor position
173 # Vertical cursor position
174 self.cury = 0
174 self.cury = 0
175
175
176 # Horizontal cursor position
176 # Horizontal cursor position
177 self.curx = 0
177 self.curx = 0
178
178
179 # Index of first data column
179 # Index of first data column
180 self.datastartx = 0
180 self.datastartx = 0
181
181
182 # Index of first data line
182 # Index of first data line
183 self.datastarty = 0
183 self.datastarty = 0
184
184
185 # height of the data display area
185 # height of the data display area
186 self.mainsizey = mainsizey
186 self.mainsizey = mainsizey
187
187
188 # width of the data display area (changes when scrolling)
188 # width of the data display area (changes when scrolling)
189 self.mainsizex = 0
189 self.mainsizex = 0
190
190
191 # Size of row number (changes when scrolling)
191 # Size of row number (changes when scrolling)
192 self.numbersizex = 0
192 self.numbersizex = 0
193
193
194 # Attributes to display (in this order)
194 # Attributes to display (in this order)
195 self.displayattrs = []
195 self.displayattrs = []
196
196
197 # index and attribute under the cursor
197 # index and attribute under the cursor
198 self.displayattr = (None, ipipe.noitem)
198 self.displayattr = (None, ipipe.noitem)
199
199
200 # Maps attributes to column widths
200 # Maps attributes to column widths
201 self.colwidths = {}
201 self.colwidths = {}
202
202
203 # Set of hidden attributes
203 # Set of hidden attributes
204 self.hiddenattrs = set()
204 self.hiddenattrs = set()
205
205
206 # This takes care of all the caches etc.
206 # This takes care of all the caches etc.
207 self.moveto(0, 0, refresh=True)
207 self.moveto(0, 0, refresh=True)
208
208
209 def fetch(self, count):
209 def fetch(self, count):
210 # Try to fill ``self.items`` with at least ``count`` objects.
210 # Try to fill ``self.items`` with at least ``count`` objects.
211 have = len(self.items)
211 have = len(self.items)
212 while not self.exhausted and have < count:
212 while not self.exhausted and have < count:
213 try:
213 try:
214 item = self.iterator.next()
214 item = self.iterator.next()
215 except StopIteration:
215 except StopIteration:
216 self.exhausted = True
216 self.exhausted = True
217 break
217 break
218 except (KeyboardInterrupt, SystemExit):
218 except (KeyboardInterrupt, SystemExit):
219 raise
219 raise
220 except Exception, exc:
220 except Exception, exc:
221 have += 1
221 have += 1
222 self.items.append(_BrowserCachedItem(exc))
222 self.items.append(_BrowserCachedItem(exc))
223 self.exhausted = True
223 self.exhausted = True
224 break
224 break
225 else:
225 else:
226 have += 1
226 have += 1
227 self.items.append(_BrowserCachedItem(item))
227 self.items.append(_BrowserCachedItem(item))
228
228
229 def calcdisplayattrs(self):
229 def calcdisplayattrs(self):
230 # Calculate which attributes are available from the objects that are
230 # Calculate which attributes are available from the objects that are
231 # currently visible on screen (and store it in ``self.displayattrs``)
231 # currently visible on screen (and store it in ``self.displayattrs``)
232
232
233 attrs = set()
233 attrs = set()
234 self.displayattrs = []
234 self.displayattrs = []
235 if self.attrs:
235 if self.attrs:
236 # If the browser object specifies a fixed list of attributes,
236 # If the browser object specifies a fixed list of attributes,
237 # simply use it (removing hidden attributes).
237 # simply use it (removing hidden attributes).
238 for attr in self.attrs:
238 for attr in self.attrs:
239 attr = ipipe.upgradexattr(attr)
239 attr = ipipe.upgradexattr(attr)
240 if attr not in attrs and attr not in self.hiddenattrs:
240 if attr not in attrs and attr not in self.hiddenattrs:
241 self.displayattrs.append(attr)
241 self.displayattrs.append(attr)
242 attrs.add(attr)
242 attrs.add(attr)
243 else:
243 else:
244 endy = min(self.datastarty+self.mainsizey, len(self.items))
244 endy = min(self.datastarty+self.mainsizey, len(self.items))
245 for i in xrange(self.datastarty, endy):
245 for i in xrange(self.datastarty, endy):
246 for attr in ipipe.xattrs(self.items[i].item, "default"):
246 for attr in ipipe.xattrs(self.items[i].item, "default"):
247 if attr not in attrs and attr not in self.hiddenattrs:
247 if attr not in attrs and attr not in self.hiddenattrs:
248 self.displayattrs.append(attr)
248 self.displayattrs.append(attr)
249 attrs.add(attr)
249 attrs.add(attr)
250
250
251 def getrow(self, i):
251 def getrow(self, i):
252 # Return a dictionary with the attributes for the object
252 # Return a dictionary with the attributes for the object
253 # ``self.items[i]``. Attribute names are taken from
253 # ``self.items[i]``. Attribute names are taken from
254 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
254 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
255 # called before.
255 # called before.
256 row = {}
256 row = {}
257 item = self.items[i].item
257 item = self.items[i].item
258 for attr in self.displayattrs:
258 for attr in self.displayattrs:
259 try:
259 try:
260 value = attr.value(item)
260 value = attr.value(item)
261 except (KeyboardInterrupt, SystemExit):
261 except (KeyboardInterrupt, SystemExit):
262 raise
262 raise
263 except Exception, exc:
263 except Exception, exc:
264 value = exc
264 value = exc
265 # only store attribute if it exists (or we got an exception)
265 # only store attribute if it exists (or we got an exception)
266 if value is not ipipe.noitem:
266 if value is not ipipe.noitem:
267 # remember alignment, length and colored text
267 # remember alignment, length and colored text
268 row[attr] = ipipe.xformat(value, "cell", self.browser.maxattrlength)
268 row[attr] = ipipe.xformat(value, "cell", self.browser.maxattrlength)
269 return row
269 return row
270
270
271 def calcwidths(self):
271 def calcwidths(self):
272 # Recalculate the displayed fields and their widths.
272 # Recalculate the displayed fields and their widths.
273 # ``calcdisplayattrs()'' must have been called and the cache
273 # ``calcdisplayattrs()'' must have been called and the cache
274 # for attributes of the objects on screen (``self.displayrows``)
274 # for attributes of the objects on screen (``self.displayrows``)
275 # must have been filled. This sets ``self.colwidths`` which maps
275 # must have been filled. This sets ``self.colwidths`` which maps
276 # attribute descriptors to widths.
276 # attribute descriptors to widths.
277 self.colwidths = {}
277 self.colwidths = {}
278 for row in self.displayrows:
278 for row in self.displayrows:
279 for attr in self.displayattrs:
279 for attr in self.displayattrs:
280 try:
280 try:
281 length = row[attr][1]
281 length = row[attr][1]
282 except KeyError:
282 except KeyError:
283 length = 0
283 length = 0
284 # always add attribute to colwidths, even if it doesn't exist
284 # always add attribute to colwidths, even if it doesn't exist
285 if attr not in self.colwidths:
285 if attr not in self.colwidths:
286 self.colwidths[attr] = len(attr.name())
286 self.colwidths[attr] = len(attr.name())
287 newwidth = max(self.colwidths[attr], length)
287 newwidth = max(self.colwidths[attr], length)
288 self.colwidths[attr] = newwidth
288 self.colwidths[attr] = newwidth
289
289
290 # How many characters do we need to paint the largest item number?
290 # How many characters do we need to paint the largest item number?
291 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
291 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
292 # How must space have we got to display data?
292 # How must space have we got to display data?
293 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
293 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
294 # width of all columns
294 # width of all columns
295 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
295 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
296
296
297 def calcdisplayattr(self):
297 def calcdisplayattr(self):
298 # Find out which attribute the cursor is on and store this
298 # Find out which attribute the cursor is on and store this
299 # information in ``self.displayattr``.
299 # information in ``self.displayattr``.
300 pos = 0
300 pos = 0
301 for (i, attr) in enumerate(self.displayattrs):
301 for (i, attr) in enumerate(self.displayattrs):
302 if pos+self.colwidths[attr] >= self.curx:
302 if pos+self.colwidths[attr] >= self.curx:
303 self.displayattr = (i, attr)
303 self.displayattr = (i, attr)
304 break
304 break
305 pos += self.colwidths[attr]+1
305 pos += self.colwidths[attr]+1
306 else:
306 else:
307 self.displayattr = (None, ipipe.noitem)
307 self.displayattr = (None, ipipe.noitem)
308
308
309 def moveto(self, x, y, refresh=False):
309 def moveto(self, x, y, refresh=False):
310 # Move the cursor to the position ``(x,y)`` (in data coordinates,
310 # Move the cursor to the position ``(x,y)`` (in data coordinates,
311 # not in screen coordinates). If ``refresh`` is true, all cached
311 # not in screen coordinates). If ``refresh`` is true, all cached
312 # values will be recalculated (e.g. because the list has been
312 # values will be recalculated (e.g. because the list has been
313 # resorted, so screen positions etc. are no longer valid).
313 # resorted, so screen positions etc. are no longer valid).
314 olddatastarty = self.datastarty
314 olddatastarty = self.datastarty
315 oldx = self.curx
315 oldx = self.curx
316 oldy = self.cury
316 oldy = self.cury
317 x = int(x+0.5)
317 x = int(x+0.5)
318 y = int(y+0.5)
318 y = int(y+0.5)
319 newx = x # remember where we wanted to move
319 newx = x # remember where we wanted to move
320 newy = y # remember where we wanted to move
320 newy = y # remember where we wanted to move
321
321
322 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
322 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
323 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
323 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
324
324
325 # Make sure that the cursor didn't leave the main area vertically
325 # Make sure that the cursor didn't leave the main area vertically
326 if y < 0:
326 if y < 0:
327 y = 0
327 y = 0
328 # try to get enough items to fill the screen
328 # try to get enough items to fill the screen
329 self.fetch(max(y+scrollbordery+1, self.mainsizey))
329 self.fetch(max(y+scrollbordery+1, self.mainsizey))
330 if y >= len(self.items):
330 if y >= len(self.items):
331 y = max(0, len(self.items)-1)
331 y = max(0, len(self.items)-1)
332
332
333 # Make sure that the cursor stays on screen vertically
333 # Make sure that the cursor stays on screen vertically
334 if y < self.datastarty+scrollbordery:
334 if y < self.datastarty+scrollbordery:
335 self.datastarty = max(0, y-scrollbordery)
335 self.datastarty = max(0, y-scrollbordery)
336 elif y >= self.datastarty+self.mainsizey-scrollbordery:
336 elif y >= self.datastarty+self.mainsizey-scrollbordery:
337 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
337 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
338 len(self.items)-self.mainsizey))
338 len(self.items)-self.mainsizey))
339
339
340 if refresh: # Do we need to refresh the complete display?
340 if refresh: # Do we need to refresh the complete display?
341 self.calcdisplayattrs()
341 self.calcdisplayattrs()
342 endy = min(self.datastarty+self.mainsizey, len(self.items))
342 endy = min(self.datastarty+self.mainsizey, len(self.items))
343 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
343 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
344 self.calcwidths()
344 self.calcwidths()
345 # Did we scroll vertically => update displayrows
345 # Did we scroll vertically => update displayrows
346 # and various other attributes
346 # and various other attributes
347 elif self.datastarty != olddatastarty:
347 elif self.datastarty != olddatastarty:
348 # Recalculate which attributes we have to display
348 # Recalculate which attributes we have to display
349 olddisplayattrs = self.displayattrs
349 olddisplayattrs = self.displayattrs
350 self.calcdisplayattrs()
350 self.calcdisplayattrs()
351 # If there are new attributes, recreate the cache
351 # If there are new attributes, recreate the cache
352 if self.displayattrs != olddisplayattrs:
352 if self.displayattrs != olddisplayattrs:
353 endy = min(self.datastarty+self.mainsizey, len(self.items))
353 endy = min(self.datastarty+self.mainsizey, len(self.items))
354 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
354 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
355 elif self.datastarty<olddatastarty: # we did scroll up
355 elif self.datastarty<olddatastarty: # we did scroll up
356 # drop rows from the end
356 # drop rows from the end
357 del self.displayrows[self.datastarty-olddatastarty:]
357 del self.displayrows[self.datastarty-olddatastarty:]
358 # fetch new items
358 # fetch new items
359 for i in xrange(min(olddatastarty, self.datastarty+self.mainsizey)-1,
359 for i in xrange(min(olddatastarty, self.datastarty+self.mainsizey)-1,
360 self.datastarty-1, -1):
360 self.datastarty-1, -1):
361 try:
361 try:
362 row = self.getrow(i)
362 row = self.getrow(i)
363 except IndexError:
363 except IndexError:
364 # we didn't have enough objects to fill the screen
364 # we didn't have enough objects to fill the screen
365 break
365 break
366 self.displayrows.insert(0, row)
366 self.displayrows.insert(0, row)
367 else: # we did scroll down
367 else: # we did scroll down
368 # drop rows from the start
368 # drop rows from the start
369 del self.displayrows[:self.datastarty-olddatastarty]
369 del self.displayrows[:self.datastarty-olddatastarty]
370 # fetch new items
370 # fetch new items
371 for i in xrange(max(olddatastarty+self.mainsizey, self.datastarty),
371 for i in xrange(max(olddatastarty+self.mainsizey, self.datastarty),
372 self.datastarty+self.mainsizey):
372 self.datastarty+self.mainsizey):
373 try:
373 try:
374 row = self.getrow(i)
374 row = self.getrow(i)
375 except IndexError:
375 except IndexError:
376 # we didn't have enough objects to fill the screen
376 # we didn't have enough objects to fill the screen
377 break
377 break
378 self.displayrows.append(row)
378 self.displayrows.append(row)
379 self.calcwidths()
379 self.calcwidths()
380
380
381 # Make sure that the cursor didn't leave the data area horizontally
381 # Make sure that the cursor didn't leave the data area horizontally
382 if x < 0:
382 if x < 0:
383 x = 0
383 x = 0
384 elif x >= self.datasizex:
384 elif x >= self.datasizex:
385 x = max(0, self.datasizex-1)
385 x = max(0, self.datasizex-1)
386
386
387 # Make sure that the cursor stays on screen horizontally
387 # Make sure that the cursor stays on screen horizontally
388 if x < self.datastartx+scrollborderx:
388 if x < self.datastartx+scrollborderx:
389 self.datastartx = max(0, x-scrollborderx)
389 self.datastartx = max(0, x-scrollborderx)
390 elif x >= self.datastartx+self.mainsizex-scrollborderx:
390 elif x >= self.datastartx+self.mainsizex-scrollborderx:
391 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
391 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
392 self.datasizex-self.mainsizex))
392 self.datasizex-self.mainsizex))
393
393
394 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
394 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
395 self.browser.beep()
395 self.browser.beep()
396 else:
396 else:
397 self.curx = x
397 self.curx = x
398 self.cury = y
398 self.cury = y
399 self.calcdisplayattr()
399 self.calcdisplayattr()
400
400
401 def sort(self, key, reverse=False):
401 def sort(self, key, reverse=False):
402 """
402 """
403 Sort the currently list of items using the key function ``key``. If
403 Sort the currently list of items using the key function ``key``. If
404 ``reverse`` is true the sort order is reversed.
404 ``reverse`` is true the sort order is reversed.
405 """
405 """
406 curitem = self.items[self.cury] # Remember where the cursor is now
406 curitem = self.items[self.cury] # Remember where the cursor is now
407
407
408 # Sort items
408 # Sort items
409 def realkey(item):
409 def realkey(item):
410 return key(item.item)
410 return key(item.item)
411 self.items = ipipe.deque(sorted(self.items, key=realkey, reverse=reverse))
411 self.items = ipipe.deque(sorted(self.items, key=realkey, reverse=reverse))
412
412
413 # Find out where the object under the cursor went
413 # Find out where the object under the cursor went
414 cury = self.cury
414 cury = self.cury
415 for (i, item) in enumerate(self.items):
415 for (i, item) in enumerate(self.items):
416 if item is curitem:
416 if item is curitem:
417 cury = i
417 cury = i
418 break
418 break
419
419
420 self.moveto(self.curx, cury, refresh=True)
420 self.moveto(self.curx, cury, refresh=True)
421
421
422 def refresh(self):
422 def refresh(self):
423 """
423 """
424 Restart iterating the input.
424 Restart iterating the input.
425 """
425 """
426 self.iterator = ipipe.xiter(self.input)
426 self.iterator = ipipe.xiter(self.input)
427 self.items.clear()
427 self.items.clear()
428 self.exhausted = False
428 self.exhausted = False
429 self.datastartx = self.datastarty = 0
429 self.datastartx = self.datastarty = 0
430 self.moveto(0, 0, refresh=True)
430 self.moveto(0, 0, refresh=True)
431
431
432 def refreshfind(self):
432 def refreshfind(self):
433 """
433 """
434 Restart iterating the input and go back to the same object as before
434 Restart iterating the input and go back to the same object as before
435 (if it can be found in the new iterator).
435 (if it can be found in the new iterator).
436 """
436 """
437 try:
437 try:
438 oldobject = self.items[self.cury].item
438 oldobject = self.items[self.cury].item
439 except IndexError:
439 except IndexError:
440 oldobject = ipipe.noitem
440 oldobject = ipipe.noitem
441 self.iterator = ipipe.xiter(self.input)
441 self.iterator = ipipe.xiter(self.input)
442 self.items.clear()
442 self.items.clear()
443 self.exhausted = False
443 self.exhausted = False
444 while True:
444 while True:
445 self.fetch(len(self.items)+1)
445 self.fetch(len(self.items)+1)
446 if self.exhausted:
446 if self.exhausted:
447 curses.beep()
447 curses.beep()
448 self.datastartx = self.datastarty = 0
448 self.datastartx = self.datastarty = 0
449 self.moveto(self.curx, 0, refresh=True)
449 self.moveto(self.curx, 0, refresh=True)
450 break
450 break
451 if self.items[-1].item == oldobject:
451 if self.items[-1].item == oldobject:
452 self.datastartx = self.datastarty = 0
452 self.datastartx = self.datastarty = 0
453 self.moveto(self.curx, len(self.items)-1, refresh=True)
453 self.moveto(self.curx, len(self.items)-1, refresh=True)
454 break
454 break
455
455
456
456
457 class _CommandInput(object):
457 class _CommandInput(object):
458 keymap = Keymap()
458 keymap = Keymap()
459 keymap.register("left", curses.KEY_LEFT)
459 keymap.register("left", curses.KEY_LEFT)
460 keymap.register("right", curses.KEY_RIGHT)
460 keymap.register("right", curses.KEY_RIGHT)
461 keymap.register("home", curses.KEY_HOME, "\x01") # Ctrl-A
461 keymap.register("home", curses.KEY_HOME, "\x01") # Ctrl-A
462 keymap.register("end", curses.KEY_END, "\x05") # Ctrl-E
462 keymap.register("end", curses.KEY_END, "\x05") # Ctrl-E
463 # FIXME: What's happening here?
463 # FIXME: What's happening here?
464 keymap.register("backspace", curses.KEY_BACKSPACE, "\x08\x7f")
464 keymap.register("backspace", curses.KEY_BACKSPACE, "\x08\x7f")
465 keymap.register("delete", curses.KEY_DC)
465 keymap.register("delete", curses.KEY_DC)
466 keymap.register("delend", 0x0b) # Ctrl-K
466 keymap.register("delend", 0x0b) # Ctrl-K
467 keymap.register("execute", "\r\n")
467 keymap.register("execute", "\r\n")
468 keymap.register("up", curses.KEY_UP)
468 keymap.register("up", curses.KEY_UP)
469 keymap.register("down", curses.KEY_DOWN)
469 keymap.register("down", curses.KEY_DOWN)
470 keymap.register("incsearchup", curses.KEY_PPAGE)
470 keymap.register("incsearchup", curses.KEY_PPAGE)
471 keymap.register("incsearchdown", curses.KEY_NPAGE)
471 keymap.register("incsearchdown", curses.KEY_NPAGE)
472 keymap.register("exit", "\x18"), # Ctrl-X
472 keymap.register("exit", "\x18"), # Ctrl-X
473
473
474 def __init__(self, prompt):
474 def __init__(self, prompt):
475 self.prompt = prompt
475 self.prompt = prompt
476 self.history = []
476 self.history = []
477 self.maxhistory = 100
477 self.maxhistory = 100
478 self.input = ""
478 self.input = ""
479 self.curx = 0
479 self.curx = 0
480 self.cury = -1 # blank line
480 self.cury = -1 # blank line
481
481
482 def start(self):
482 def start(self):
483 self.input = ""
483 self.input = ""
484 self.curx = 0
484 self.curx = 0
485 self.cury = -1 # blank line
485 self.cury = -1 # blank line
486
486
487 def handlekey(self, browser, key):
487 def handlekey(self, browser, key):
488 cmdname = self.keymap.get(key, None)
488 cmdname = self.keymap.get(key, None)
489 if cmdname is not None:
489 if cmdname is not None:
490 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
490 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
491 if cmdfunc is not None:
491 if cmdfunc is not None:
492 return cmdfunc(browser)
492 return cmdfunc(browser)
493 curses.beep()
493 curses.beep()
494 elif key != -1:
494 elif key != -1:
495 try:
495 try:
496 char = chr(key)
496 char = chr(key)
497 except ValueError:
497 except ValueError:
498 curses.beep()
498 curses.beep()
499 else:
499 else:
500 return self.handlechar(browser, char)
500 return self.handlechar(browser, char)
501
501
502 def handlechar(self, browser, char):
502 def handlechar(self, browser, char):
503 self.input = self.input[:self.curx] + char + self.input[self.curx:]
503 self.input = self.input[:self.curx] + char + self.input[self.curx:]
504 self.curx += 1
504 self.curx += 1
505 return True
505 return True
506
506
507 def dohistory(self):
507 def dohistory(self):
508 self.history.insert(0, self.input)
508 self.history.insert(0, self.input)
509 del self.history[:-self.maxhistory]
509 del self.history[:-self.maxhistory]
510
510
511 def cmd_backspace(self, browser):
511 def cmd_backspace(self, browser):
512 if self.curx:
512 if self.curx:
513 self.input = self.input[:self.curx-1] + self.input[self.curx:]
513 self.input = self.input[:self.curx-1] + self.input[self.curx:]
514 self.curx -= 1
514 self.curx -= 1
515 return True
515 return True
516 else:
516 else:
517 curses.beep()
517 curses.beep()
518
518
519 def cmd_delete(self, browser):
519 def cmd_delete(self, browser):
520 if self.curx<len(self.input):
520 if self.curx<len(self.input):
521 self.input = self.input[:self.curx] + self.input[self.curx+1:]
521 self.input = self.input[:self.curx] + self.input[self.curx+1:]
522 return True
522 return True
523 else:
523 else:
524 curses.beep()
524 curses.beep()
525
525
526 def cmd_delend(self, browser):
526 def cmd_delend(self, browser):
527 if self.curx<len(self.input):
527 if self.curx<len(self.input):
528 self.input = self.input[:self.curx]
528 self.input = self.input[:self.curx]
529 return True
529 return True
530
530
531 def cmd_left(self, browser):
531 def cmd_left(self, browser):
532 if self.curx:
532 if self.curx:
533 self.curx -= 1
533 self.curx -= 1
534 return True
534 return True
535 else:
535 else:
536 curses.beep()
536 curses.beep()
537
537
538 def cmd_right(self, browser):
538 def cmd_right(self, browser):
539 if self.curx < len(self.input):
539 if self.curx < len(self.input):
540 self.curx += 1
540 self.curx += 1
541 return True
541 return True
542 else:
542 else:
543 curses.beep()
543 curses.beep()
544
544
545 def cmd_home(self, browser):
545 def cmd_home(self, browser):
546 if self.curx:
546 if self.curx:
547 self.curx = 0
547 self.curx = 0
548 return True
548 return True
549 else:
549 else:
550 curses.beep()
550 curses.beep()
551
551
552 def cmd_end(self, browser):
552 def cmd_end(self, browser):
553 if self.curx < len(self.input):
553 if self.curx < len(self.input):
554 self.curx = len(self.input)
554 self.curx = len(self.input)
555 return True
555 return True
556 else:
556 else:
557 curses.beep()
557 curses.beep()
558
558
559 def cmd_up(self, browser):
559 def cmd_up(self, browser):
560 if self.cury < len(self.history)-1:
560 if self.cury < len(self.history)-1:
561 self.cury += 1
561 self.cury += 1
562 self.input = self.history[self.cury]
562 self.input = self.history[self.cury]
563 self.curx = len(self.input)
563 self.curx = len(self.input)
564 return True
564 return True
565 else:
565 else:
566 curses.beep()
566 curses.beep()
567
567
568 def cmd_down(self, browser):
568 def cmd_down(self, browser):
569 if self.cury >= 0:
569 if self.cury >= 0:
570 self.cury -= 1
570 self.cury -= 1
571 if self.cury>=0:
571 if self.cury>=0:
572 self.input = self.history[self.cury]
572 self.input = self.history[self.cury]
573 else:
573 else:
574 self.input = ""
574 self.input = ""
575 self.curx = len(self.input)
575 self.curx = len(self.input)
576 return True
576 return True
577 else:
577 else:
578 curses.beep()
578 curses.beep()
579
579
580 def cmd_incsearchup(self, browser):
580 def cmd_incsearchup(self, browser):
581 prefix = self.input[:self.curx]
581 prefix = self.input[:self.curx]
582 cury = self.cury
582 cury = self.cury
583 while True:
583 while True:
584 cury += 1
584 cury += 1
585 if cury >= len(self.history):
585 if cury >= len(self.history):
586 break
586 break
587 if self.history[cury].startswith(prefix):
587 if self.history[cury].startswith(prefix):
588 self.input = self.history[cury]
588 self.input = self.history[cury]
589 self.cury = cury
589 self.cury = cury
590 return True
590 return True
591 curses.beep()
591 curses.beep()
592
592
593 def cmd_incsearchdown(self, browser):
593 def cmd_incsearchdown(self, browser):
594 prefix = self.input[:self.curx]
594 prefix = self.input[:self.curx]
595 cury = self.cury
595 cury = self.cury
596 while True:
596 while True:
597 cury -= 1
597 cury -= 1
598 if cury <= 0:
598 if cury <= 0:
599 break
599 break
600 if self.history[cury].startswith(prefix):
600 if self.history[cury].startswith(prefix):
601 self.input = self.history[self.cury]
601 self.input = self.history[self.cury]
602 self.cury = cury
602 self.cury = cury
603 return True
603 return True
604 curses.beep()
604 curses.beep()
605
605
606 def cmd_exit(self, browser):
606 def cmd_exit(self, browser):
607 browser.mode = "default"
607 browser.mode = "default"
608 return True
608 return True
609
609
610 def cmd_execute(self, browser):
610 def cmd_execute(self, browser):
611 raise NotImplementedError
611 raise NotImplementedError
612
612
613
613
614 class _CommandGoto(_CommandInput):
614 class _CommandGoto(_CommandInput):
615 def __init__(self):
615 def __init__(self):
616 _CommandInput.__init__(self, "goto object #")
616 _CommandInput.__init__(self, "goto object #")
617
617
618 def handlechar(self, browser, char):
618 def handlechar(self, browser, char):
619 # Only accept digits
619 # Only accept digits
620 if not "0" <= char <= "9":
620 if not "0" <= char <= "9":
621 curses.beep()
621 curses.beep()
622 else:
622 else:
623 return _CommandInput.handlechar(self, browser, char)
623 return _CommandInput.handlechar(self, browser, char)
624
624
625 def cmd_execute(self, browser):
625 def cmd_execute(self, browser):
626 level = browser.levels[-1]
626 level = browser.levels[-1]
627 if self.input:
627 if self.input:
628 self.dohistory()
628 self.dohistory()
629 level.moveto(level.curx, int(self.input))
629 level.moveto(level.curx, int(self.input))
630 browser.mode = "default"
630 browser.mode = "default"
631 return True
631 return True
632
632
633
633
634 class _CommandFind(_CommandInput):
634 class _CommandFind(_CommandInput):
635 def __init__(self):
635 def __init__(self):
636 _CommandInput.__init__(self, "find expression")
636 _CommandInput.__init__(self, "find expression")
637
637
638 def cmd_execute(self, browser):
638 def cmd_execute(self, browser):
639 level = browser.levels[-1]
639 level = browser.levels[-1]
640 if self.input:
640 if self.input:
641 self.dohistory()
641 self.dohistory()
642 while True:
642 while True:
643 cury = level.cury
643 cury = level.cury
644 level.moveto(level.curx, cury+1)
644 level.moveto(level.curx, cury+1)
645 if cury == level.cury:
645 if cury == level.cury:
646 curses.beep()
646 curses.beep()
647 break # hit end
647 break # hit end
648 item = level.items[level.cury].item
648 item = level.items[level.cury].item
649 try:
649 try:
650 globals = ipipe.getglobals(None)
650 globals = ipipe.getglobals(None)
651 if eval(self.input, globals, ipipe.AttrNamespace(item)):
651 if eval(self.input, globals, ipipe.AttrNamespace(item)):
652 break # found something
652 break # found something
653 except (KeyboardInterrupt, SystemExit):
653 except (KeyboardInterrupt, SystemExit):
654 raise
654 raise
655 except Exception, exc:
655 except Exception, exc:
656 browser.report(exc)
656 browser.report(exc)
657 curses.beep()
657 curses.beep()
658 break # break on error
658 break # break on error
659 browser.mode = "default"
659 browser.mode = "default"
660 return True
660 return True
661
661
662
662
663 class _CommandFindBackwards(_CommandInput):
663 class _CommandFindBackwards(_CommandInput):
664 def __init__(self):
664 def __init__(self):
665 _CommandInput.__init__(self, "find backwards expression")
665 _CommandInput.__init__(self, "find backwards expression")
666
666
667 def cmd_execute(self, browser):
667 def cmd_execute(self, browser):
668 level = browser.levels[-1]
668 level = browser.levels[-1]
669 if self.input:
669 if self.input:
670 self.dohistory()
670 self.dohistory()
671 while level.cury:
671 while level.cury:
672 level.moveto(level.curx, level.cury-1)
672 level.moveto(level.curx, level.cury-1)
673 item = level.items[level.cury].item
673 item = level.items[level.cury].item
674 try:
674 try:
675 globals = ipipe.getglobals(None)
675 globals = ipipe.getglobals(None)
676 if eval(self.input, globals, ipipe.AttrNamespace(item)):
676 if eval(self.input, globals, ipipe.AttrNamespace(item)):
677 break # found something
677 break # found something
678 except (KeyboardInterrupt, SystemExit):
678 except (KeyboardInterrupt, SystemExit):
679 raise
679 raise
680 except Exception, exc:
680 except Exception, exc:
681 browser.report(exc)
681 browser.report(exc)
682 curses.beep()
682 curses.beep()
683 break # break on error
683 break # break on error
684 else:
684 else:
685 curses.beep()
685 curses.beep()
686 browser.mode = "default"
686 browser.mode = "default"
687 return True
687 return True
688
688
689
689
690 class ibrowse(ipipe.Display):
690 class ibrowse(ipipe.Display):
691 # Show this many lines from the previous screen when paging horizontally
691 # Show this many lines from the previous screen when paging horizontally
692 pageoverlapx = 1
692 pageoverlapx = 1
693
693
694 # Show this many lines from the previous screen when paging vertically
694 # Show this many lines from the previous screen when paging vertically
695 pageoverlapy = 1
695 pageoverlapy = 1
696
696
697 # Start scrolling when the cursor is less than this number of columns
697 # Start scrolling when the cursor is less than this number of columns
698 # away from the left or right screen edge
698 # away from the left or right screen edge
699 scrollborderx = 10
699 scrollborderx = 10
700
700
701 # Start scrolling when the cursor is less than this number of lines
701 # Start scrolling when the cursor is less than this number of lines
702 # away from the top or bottom screen edge
702 # away from the top or bottom screen edge
703 scrollbordery = 5
703 scrollbordery = 5
704
704
705 # Accelerate by this factor when scrolling horizontally
705 # Accelerate by this factor when scrolling horizontally
706 acceleratex = 1.05
706 acceleratex = 1.05
707
707
708 # Accelerate by this factor when scrolling vertically
708 # Accelerate by this factor when scrolling vertically
709 acceleratey = 1.05
709 acceleratey = 1.05
710
710
711 # The maximum horizontal scroll speed
711 # The maximum horizontal scroll speed
712 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
712 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
713 maxspeedx = 0.5
713 maxspeedx = 0.5
714
714
715 # The maximum vertical scroll speed
715 # The maximum vertical scroll speed
716 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
716 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
717 maxspeedy = 0.5
717 maxspeedy = 0.5
718
718
719 # The maximum number of header lines for browser level
719 # The maximum number of header lines for browser level
720 # if the nesting is deeper, only the innermost levels are displayed
720 # if the nesting is deeper, only the innermost levels are displayed
721 maxheaders = 5
721 maxheaders = 5
722
722
723 # The approximate maximum length of a column entry
723 # The approximate maximum length of a column entry
724 maxattrlength = 200
724 maxattrlength = 200
725
725
726 # Styles for various parts of the GUI
726 # Styles for various parts of the GUI
727 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
727 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
728 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
728 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
729 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
729 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
730 style_colheader = astyle.Style.fromstr("blue:white:reverse")
730 style_colheader = astyle.Style.fromstr("blue:white:reverse")
731 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
731 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
732 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
732 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
733 style_number = astyle.Style.fromstr("blue:white:reverse")
733 style_number = astyle.Style.fromstr("blue:white:reverse")
734 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
734 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
735 style_sep = astyle.Style.fromstr("blue:black")
735 style_sep = astyle.Style.fromstr("blue:black")
736 style_data = astyle.Style.fromstr("white:black")
736 style_data = astyle.Style.fromstr("white:black")
737 style_datapad = astyle.Style.fromstr("blue:black:bold")
737 style_datapad = astyle.Style.fromstr("blue:black:bold")
738 style_footer = astyle.Style.fromstr("black:white")
738 style_footer = astyle.Style.fromstr("black:white")
739 style_report = astyle.Style.fromstr("white:black")
739 style_report = astyle.Style.fromstr("white:black")
740
740
741 # Column separator in header
741 # Column separator in header
742 headersepchar = "|"
742 headersepchar = "|"
743
743
744 # Character for padding data cell entries
744 # Character for padding data cell entries
745 datapadchar = "."
745 datapadchar = "."
746
746
747 # Column separator in data area
747 # Column separator in data area
748 datasepchar = "|"
748 datasepchar = "|"
749
749
750 # Character to use for "empty" cell (i.e. for non-existing attributes)
750 # Character to use for "empty" cell (i.e. for non-existing attributes)
751 nodatachar = "-"
751 nodatachar = "-"
752
752
753 # Prompts for modes that require keyboard input
753 # Prompts for modes that require keyboard input
754 prompts = {
754 prompts = {
755 "goto": _CommandGoto(),
755 "goto": _CommandGoto(),
756 "find": _CommandFind(),
756 "find": _CommandFind(),
757 "findbackwards": _CommandFindBackwards()
757 "findbackwards": _CommandFindBackwards()
758 }
758 }
759
759
760 # Maps curses key codes to "function" names
760 # Maps curses key codes to "function" names
761 keymap = Keymap()
761 keymap = Keymap()
762 keymap.register("quit", "q")
762 keymap.register("quit", "q")
763 keymap.register("up", curses.KEY_UP)
763 keymap.register("up", curses.KEY_UP)
764 keymap.register("down", curses.KEY_DOWN)
764 keymap.register("down", curses.KEY_DOWN)
765 keymap.register("pageup", curses.KEY_PPAGE)
765 keymap.register("pageup", curses.KEY_PPAGE)
766 keymap.register("pagedown", curses.KEY_NPAGE)
766 keymap.register("pagedown", curses.KEY_NPAGE)
767 keymap.register("left", curses.KEY_LEFT)
767 keymap.register("left", curses.KEY_LEFT)
768 keymap.register("right", curses.KEY_RIGHT)
768 keymap.register("right", curses.KEY_RIGHT)
769 keymap.register("home", curses.KEY_HOME, "\x01")
769 keymap.register("home", curses.KEY_HOME, "\x01")
770 keymap.register("end", curses.KEY_END, "\x05")
770 keymap.register("end", curses.KEY_END, "\x05")
771 keymap.register("prevattr", "<\x1b")
771 keymap.register("prevattr", "<\x1b")
772 keymap.register("nextattr", ">\t")
772 keymap.register("nextattr", ">\t")
773 keymap.register("pick", "p")
773 keymap.register("pick", "p")
774 keymap.register("pickattr", "P")
774 keymap.register("pickattr", "P")
775 keymap.register("pickallattrs", "C")
775 keymap.register("pickallattrs", "C")
776 keymap.register("pickmarked", "m")
776 keymap.register("pickmarked", "m")
777 keymap.register("pickmarkedattr", "M")
777 keymap.register("pickmarkedattr", "M")
778 keymap.register("pickinput", "i")
778 keymap.register("pickinput", "i")
779 keymap.register("pickinputattr", "I")
779 keymap.register("pickinputattr", "I")
780 keymap.register("hideattr", "h")
780 keymap.register("hideattr", "h")
781 keymap.register("unhideattrs", "H")
781 keymap.register("unhideattrs", "H")
782 keymap.register("help", "?")
782 keymap.register("help", "?")
783 keymap.register("enter", "\r\n")
783 keymap.register("enter", "\r\n")
784 keymap.register("enterattr", "E")
784 keymap.register("enterattr", "E")
785 # FIXME: What's happening here?
785 # FIXME: What's happening here?
786 keymap.register("leave", curses.KEY_BACKSPACE, "x\x08\x7f")
786 keymap.register("leave", curses.KEY_BACKSPACE, "x\x08\x7f")
787 keymap.register("detail", "d")
787 keymap.register("detail", "d")
788 keymap.register("detailattr", "D")
788 keymap.register("detailattr", "D")
789 keymap.register("tooglemark", " ")
789 keymap.register("tooglemark", " ")
790 keymap.register("markrange", "%")
790 keymap.register("markrange", "%")
791 keymap.register("sortattrasc", "v")
791 keymap.register("sortattrasc", "v")
792 keymap.register("sortattrdesc", "V")
792 keymap.register("sortattrdesc", "V")
793 keymap.register("goto", "g")
793 keymap.register("goto", "g")
794 keymap.register("find", "f")
794 keymap.register("find", "f")
795 keymap.register("findbackwards", "b")
795 keymap.register("findbackwards", "b")
796 keymap.register("refresh", "r")
796 keymap.register("refresh", "r")
797 keymap.register("refreshfind", "R")
797 keymap.register("refreshfind", "R")
798
798
799 def __init__(self, input=None, *attrs):
799 def __init__(self, input=None, *attrs):
800 """
800 """
801 Create a new browser. If ``attrs`` is not empty, it is the list
801 Create a new browser. If ``attrs`` is not empty, it is the list
802 of attributes that will be displayed in the browser, otherwise
802 of attributes that will be displayed in the browser, otherwise
803 these will be determined by the objects on screen.
803 these will be determined by the objects on screen.
804 """
804 """
805 ipipe.Display.__init__(self, input)
805 ipipe.Display.__init__(self, input)
806
806
807 self.attrs = attrs
807 self.attrs = attrs
808
808
809 # Stack of browser levels
809 # Stack of browser levels
810 self.levels = []
810 self.levels = []
811 # how many colums to scroll (Changes when accelerating)
811 # how many colums to scroll (Changes when accelerating)
812 self.stepx = 1.
812 self.stepx = 1.
813
813
814 # how many rows to scroll (Changes when accelerating)
814 # how many rows to scroll (Changes when accelerating)
815 self.stepy = 1.
815 self.stepy = 1.
816
816
817 # Beep on the edges of the data area? (Will be set to ``False``
817 # Beep on the edges of the data area? (Will be set to ``False``
818 # once the cursor hits the edge of the screen, so we don't get
818 # once the cursor hits the edge of the screen, so we don't get
819 # multiple beeps).
819 # multiple beeps).
820 self._dobeep = True
820 self._dobeep = True
821
821
822 # Cache for registered ``curses`` colors and styles.
822 # Cache for registered ``curses`` colors and styles.
823 self._styles = {}
823 self._styles = {}
824 self._colors = {}
824 self._colors = {}
825 self._maxcolor = 1
825 self._maxcolor = 1
826
826
827 # How many header lines do we want to paint (the numbers of levels
827 # How many header lines do we want to paint (the numbers of levels
828 # we have, but with an upper bound)
828 # we have, but with an upper bound)
829 self._headerlines = 1
829 self._headerlines = 1
830
830
831 # Index of first header line
831 # Index of first header line
832 self._firstheaderline = 0
832 self._firstheaderline = 0
833
833
834 # curses window
834 # curses window
835 self.scr = None
835 self.scr = None
836 # report in the footer line (error, executed command etc.)
836 # report in the footer line (error, executed command etc.)
837 self._report = None
837 self._report = None
838
838
839 # value to be returned to the caller (set by commands)
839 # value to be returned to the caller (set by commands)
840 self.returnvalue = None
840 self.returnvalue = None
841
841
842 # The mode the browser is in
842 # The mode the browser is in
843 # e.g. normal browsing or entering an argument for a command
843 # e.g. normal browsing or entering an argument for a command
844 self.mode = "default"
844 self.mode = "default"
845
845
846 # set by the SIGWINCH signal handler
846 # set by the SIGWINCH signal handler
847 self.resized = False
847 self.resized = False
848
848
849 def nextstepx(self, step):
849 def nextstepx(self, step):
850 """
850 """
851 Accelerate horizontally.
851 Accelerate horizontally.
852 """
852 """
853 return max(1., min(step*self.acceleratex,
853 return max(1., min(step*self.acceleratex,
854 self.maxspeedx*self.levels[-1].mainsizex))
854 self.maxspeedx*self.levels[-1].mainsizex))
855
855
856 def nextstepy(self, step):
856 def nextstepy(self, step):
857 """
857 """
858 Accelerate vertically.
858 Accelerate vertically.
859 """
859 """
860 return max(1., min(step*self.acceleratey,
860 return max(1., min(step*self.acceleratey,
861 self.maxspeedy*self.levels[-1].mainsizey))
861 self.maxspeedy*self.levels[-1].mainsizey))
862
862
863 def getstyle(self, style):
863 def getstyle(self, style):
864 """
864 """
865 Register the ``style`` with ``curses`` or get it from the cache,
865 Register the ``style`` with ``curses`` or get it from the cache,
866 if it has been registered before.
866 if it has been registered before.
867 """
867 """
868 try:
868 try:
869 return self._styles[style.fg, style.bg, style.attrs]
869 return self._styles[style.fg, style.bg, style.attrs]
870 except KeyError:
870 except KeyError:
871 attrs = 0
871 attrs = 0
872 for b in astyle.A2CURSES:
872 for b in astyle.A2CURSES:
873 if style.attrs & b:
873 if style.attrs & b:
874 attrs |= astyle.A2CURSES[b]
874 attrs |= astyle.A2CURSES[b]
875 try:
875 try:
876 color = self._colors[style.fg, style.bg]
876 color = self._colors[style.fg, style.bg]
877 except KeyError:
877 except KeyError:
878 curses.init_pair(
878 curses.init_pair(
879 self._maxcolor,
879 self._maxcolor,
880 astyle.COLOR2CURSES[style.fg],
880 astyle.COLOR2CURSES[style.fg],
881 astyle.COLOR2CURSES[style.bg]
881 astyle.COLOR2CURSES[style.bg]
882 )
882 )
883 color = curses.color_pair(self._maxcolor)
883 color = curses.color_pair(self._maxcolor)
884 self._colors[style.fg, style.bg] = color
884 self._colors[style.fg, style.bg] = color
885 self._maxcolor += 1
885 self._maxcolor += 1
886 c = color | attrs
886 c = color | attrs
887 self._styles[style.fg, style.bg, style.attrs] = c
887 self._styles[style.fg, style.bg, style.attrs] = c
888 return c
888 return c
889
889
890 def addstr(self, y, x, begx, endx, text, style):
890 def addstr(self, y, x, begx, endx, text, style):
891 """
891 """
892 A version of ``curses.addstr()`` that can handle ``x`` coordinates
892 A version of ``curses.addstr()`` that can handle ``x`` coordinates
893 that are outside the screen.
893 that are outside the screen.
894 """
894 """
895 text2 = text[max(0, begx-x):max(0, endx-x)]
895 text2 = text[max(0, begx-x):max(0, endx-x)]
896 if text2:
896 if text2:
897 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
897 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
898 return len(text)
898 return len(text)
899
899
900 def addchr(self, y, x, begx, endx, c, l, style):
900 def addchr(self, y, x, begx, endx, c, l, style):
901 x0 = max(x, begx)
901 x0 = max(x, begx)
902 x1 = min(x+l, endx)
902 x1 = min(x+l, endx)
903 if x1>x0:
903 if x1>x0:
904 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
904 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
905 return l
905 return l
906
906
907 def _calcheaderlines(self, levels):
907 def _calcheaderlines(self, levels):
908 # Calculate how many headerlines do we have to display, if we have
908 # Calculate how many headerlines do we have to display, if we have
909 # ``levels`` browser levels
909 # ``levels`` browser levels
910 if levels is None:
910 if levels is None:
911 levels = len(self.levels)
911 levels = len(self.levels)
912 self._headerlines = min(self.maxheaders, levels)
912 self._headerlines = min(self.maxheaders, levels)
913 self._firstheaderline = levels-self._headerlines
913 self._firstheaderline = levels-self._headerlines
914
914
915 def getstylehere(self, style):
915 def getstylehere(self, style):
916 """
916 """
917 Return a style for displaying the original style ``style``
917 Return a style for displaying the original style ``style``
918 in the row the cursor is on.
918 in the row the cursor is on.
919 """
919 """
920 return astyle.Style(style.fg, astyle.COLOR_BLUE, style.attrs | astyle.A_BOLD)
920 return astyle.Style(style.fg, astyle.COLOR_BLUE, style.attrs | astyle.A_BOLD)
921
921
922 def report(self, msg):
922 def report(self, msg):
923 """
923 """
924 Store the message ``msg`` for display below the footer line. This
924 Store the message ``msg`` for display below the footer line. This
925 will be displayed as soon as the screen is redrawn.
925 will be displayed as soon as the screen is redrawn.
926 """
926 """
927 self._report = msg
927 self._report = msg
928
928
929 def enter(self, item, *attrs):
929 def enter(self, item, *attrs):
930 """
930 """
931 Enter the object ``item``. If ``attrs`` is specified, it will be used
931 Enter the object ``item``. If ``attrs`` is specified, it will be used
932 as a fixed list of attributes to display.
932 as a fixed list of attributes to display.
933 """
933 """
934 if self.levels and item is self.levels[-1].input:
934 if self.levels and item is self.levels[-1].input:
935 curses.beep()
935 curses.beep()
936 self.report(CommandError("Recursion on input object"))
936 self.report(CommandError("Recursion on input object"))
937 else:
937 else:
938 oldlevels = len(self.levels)
938 oldlevels = len(self.levels)
939 self._calcheaderlines(oldlevels+1)
939 self._calcheaderlines(oldlevels+1)
940 try:
940 try:
941 level = _BrowserLevel(
941 level = _BrowserLevel(
942 self,
942 self,
943 item,
943 item,
944 self.scrsizey-1-self._headerlines-2,
944 self.scrsizey-1-self._headerlines-2,
945 *attrs
945 *attrs
946 )
946 )
947 except (KeyboardInterrupt, SystemExit):
947 except (KeyboardInterrupt, SystemExit):
948 raise
948 raise
949 except Exception, exc:
949 except Exception, exc:
950 if not self.levels:
950 if not self.levels:
951 raise
951 raise
952 self._calcheaderlines(oldlevels)
952 self._calcheaderlines(oldlevels)
953 curses.beep()
953 curses.beep()
954 self.report(exc)
954 self.report(exc)
955 else:
955 else:
956 self.levels.append(level)
956 self.levels.append(level)
957
957
958 def startkeyboardinput(self, mode):
958 def startkeyboardinput(self, mode):
959 """
959 """
960 Enter mode ``mode``, which requires keyboard input.
960 Enter mode ``mode``, which requires keyboard input.
961 """
961 """
962 self.mode = mode
962 self.mode = mode
963 self.prompts[mode].start()
963 self.prompts[mode].start()
964
964
965 def keylabel(self, keycode):
965 def keylabel(self, keycode):
966 """
966 """
967 Return a pretty name for the ``curses`` key ``keycode`` (used in the
967 Return a pretty name for the ``curses`` key ``keycode`` (used in the
968 help screen and in reports about unassigned keys).
968 help screen and in reports about unassigned keys).
969 """
969 """
970 if keycode <= 0xff:
970 if keycode <= 0xff:
971 specialsnames = {
971 specialsnames = {
972 ord("\n"): "RETURN",
972 ord("\n"): "RETURN",
973 ord(" "): "SPACE",
973 ord(" "): "SPACE",
974 ord("\t"): "TAB",
974 ord("\t"): "TAB",
975 ord("\x7f"): "DELETE",
975 ord("\x7f"): "DELETE",
976 ord("\x08"): "BACKSPACE",
976 ord("\x08"): "BACKSPACE",
977 }
977 }
978 if keycode in specialsnames:
978 if keycode in specialsnames:
979 return specialsnames[keycode]
979 return specialsnames[keycode]
980 elif 0x00 < keycode < 0x20:
980 elif 0x00 < keycode < 0x20:
981 return "CTRL-%s" % chr(keycode + 64)
981 return "CTRL-%s" % chr(keycode + 64)
982 return repr(chr(keycode))
982 return repr(chr(keycode))
983 for name in dir(curses):
983 for name in dir(curses):
984 if name.startswith("KEY_") and getattr(curses, name) == keycode:
984 if name.startswith("KEY_") and getattr(curses, name) == keycode:
985 return name
985 return name
986 return str(keycode)
986 return str(keycode)
987
987
988 def beep(self, force=False):
988 def beep(self, force=False):
989 if force or self._dobeep:
989 if force or self._dobeep:
990 curses.beep()
990 curses.beep()
991 # don't beep again (as long as the same key is pressed)
991 # don't beep again (as long as the same key is pressed)
992 self._dobeep = False
992 self._dobeep = False
993
993
994 def cmd_up(self):
994 def cmd_up(self):
995 """
995 """
996 Move the cursor to the previous row.
996 Move the cursor to the previous row.
997 """
997 """
998 level = self.levels[-1]
998 level = self.levels[-1]
999 self.report("up")
999 self.report("up")
1000 level.moveto(level.curx, level.cury-self.stepy)
1000 level.moveto(level.curx, level.cury-self.stepy)
1001
1001
1002 def cmd_down(self):
1002 def cmd_down(self):
1003 """
1003 """
1004 Move the cursor to the next row.
1004 Move the cursor to the next row.
1005 """
1005 """
1006 level = self.levels[-1]
1006 level = self.levels[-1]
1007 self.report("down")
1007 self.report("down")
1008 level.moveto(level.curx, level.cury+self.stepy)
1008 level.moveto(level.curx, level.cury+self.stepy)
1009
1009
1010 def cmd_pageup(self):
1010 def cmd_pageup(self):
1011 """
1011 """
1012 Move the cursor up one page.
1012 Move the cursor up one page.
1013 """
1013 """
1014 level = self.levels[-1]
1014 level = self.levels[-1]
1015 self.report("page up")
1015 self.report("page up")
1016 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
1016 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
1017
1017
1018 def cmd_pagedown(self):
1018 def cmd_pagedown(self):
1019 """
1019 """
1020 Move the cursor down one page.
1020 Move the cursor down one page.
1021 """
1021 """
1022 level = self.levels[-1]
1022 level = self.levels[-1]
1023 self.report("page down")
1023 self.report("page down")
1024 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
1024 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
1025
1025
1026 def cmd_left(self):
1026 def cmd_left(self):
1027 """
1027 """
1028 Move the cursor left.
1028 Move the cursor left.
1029 """
1029 """
1030 level = self.levels[-1]
1030 level = self.levels[-1]
1031 self.report("left")
1031 self.report("left")
1032 level.moveto(level.curx-self.stepx, level.cury)
1032 level.moveto(level.curx-self.stepx, level.cury)
1033
1033
1034 def cmd_right(self):
1034 def cmd_right(self):
1035 """
1035 """
1036 Move the cursor right.
1036 Move the cursor right.
1037 """
1037 """
1038 level = self.levels[-1]
1038 level = self.levels[-1]
1039 self.report("right")
1039 self.report("right")
1040 level.moveto(level.curx+self.stepx, level.cury)
1040 level.moveto(level.curx+self.stepx, level.cury)
1041
1041
1042 def cmd_home(self):
1042 def cmd_home(self):
1043 """
1043 """
1044 Move the cursor to the first column.
1044 Move the cursor to the first column.
1045 """
1045 """
1046 level = self.levels[-1]
1046 level = self.levels[-1]
1047 self.report("home")
1047 self.report("home")
1048 level.moveto(0, level.cury)
1048 level.moveto(0, level.cury)
1049
1049
1050 def cmd_end(self):
1050 def cmd_end(self):
1051 """
1051 """
1052 Move the cursor to the last column.
1052 Move the cursor to the last column.
1053 """
1053 """
1054 level = self.levels[-1]
1054 level = self.levels[-1]
1055 self.report("end")
1055 self.report("end")
1056 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
1056 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
1057
1057
1058 def cmd_prevattr(self):
1058 def cmd_prevattr(self):
1059 """
1059 """
1060 Move the cursor one attribute column to the left.
1060 Move the cursor one attribute column to the left.
1061 """
1061 """
1062 level = self.levels[-1]
1062 level = self.levels[-1]
1063 if level.displayattr[0] is None or level.displayattr[0] == 0:
1063 if level.displayattr[0] is None or level.displayattr[0] == 0:
1064 self.beep()
1064 self.beep()
1065 else:
1065 else:
1066 self.report("prevattr")
1066 self.report("prevattr")
1067 pos = 0
1067 pos = 0
1068 for (i, attrname) in enumerate(level.displayattrs):
1068 for (i, attrname) in enumerate(level.displayattrs):
1069 if i == level.displayattr[0]-1:
1069 if i == level.displayattr[0]-1:
1070 break
1070 break
1071 pos += level.colwidths[attrname] + 1
1071 pos += level.colwidths[attrname] + 1
1072 level.moveto(pos, level.cury)
1072 level.moveto(pos, level.cury)
1073
1073
1074 def cmd_nextattr(self):
1074 def cmd_nextattr(self):
1075 """
1075 """
1076 Move the cursor one attribute column to the right.
1076 Move the cursor one attribute column to the right.
1077 """
1077 """
1078 level = self.levels[-1]
1078 level = self.levels[-1]
1079 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
1079 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
1080 self.beep()
1080 self.beep()
1081 else:
1081 else:
1082 self.report("nextattr")
1082 self.report("nextattr")
1083 pos = 0
1083 pos = 0
1084 for (i, attrname) in enumerate(level.displayattrs):
1084 for (i, attrname) in enumerate(level.displayattrs):
1085 if i == level.displayattr[0]+1:
1085 if i == level.displayattr[0]+1:
1086 break
1086 break
1087 pos += level.colwidths[attrname] + 1
1087 pos += level.colwidths[attrname] + 1
1088 level.moveto(pos, level.cury)
1088 level.moveto(pos, level.cury)
1089
1089
1090 def cmd_pick(self):
1090 def cmd_pick(self):
1091 """
1091 """
1092 'Pick' the object under the cursor (i.e. the row the cursor is on).
1092 'Pick' the object under the cursor (i.e. the row the cursor is on).
1093 This leaves the browser and returns the picked object to the caller.
1093 This leaves the browser and returns the picked object to the caller.
1094 (In IPython this object will be available as the ``_`` variable.)
1094 (In IPython this object will be available as the ``_`` variable.)
1095 """
1095 """
1096 level = self.levels[-1]
1096 level = self.levels[-1]
1097 self.returnvalue = level.items[level.cury].item
1097 self.returnvalue = level.items[level.cury].item
1098 return True
1098 return True
1099
1099
1100 def cmd_pickattr(self):
1100 def cmd_pickattr(self):
1101 """
1101 """
1102 'Pick' the attribute under the cursor (i.e. the row/column the
1102 'Pick' the attribute under the cursor (i.e. the row/column the
1103 cursor is on).
1103 cursor is on).
1104 """
1104 """
1105 level = self.levels[-1]
1105 level = self.levels[-1]
1106 attr = level.displayattr[1]
1106 attr = level.displayattr[1]
1107 if attr is ipipe.noitem:
1107 if attr is ipipe.noitem:
1108 curses.beep()
1108 curses.beep()
1109 self.report(CommandError("no column under cursor"))
1109 self.report(CommandError("no column under cursor"))
1110 return
1110 return
1111 value = attr.value(level.items[level.cury].item)
1111 value = attr.value(level.items[level.cury].item)
1112 if value is ipipe.noitem:
1112 if value is ipipe.noitem:
1113 curses.beep()
1113 curses.beep()
1114 self.report(AttributeError(attr.name()))
1114 self.report(AttributeError(attr.name()))
1115 else:
1115 else:
1116 self.returnvalue = value
1116 self.returnvalue = value
1117 return True
1117 return True
1118
1118
1119 def cmd_pickallattrs(self):
1119 def cmd_pickallattrs(self):
1120 """
1120 """
1121 Pick' the complete column under the cursor (i.e. the attribute under
1121 Pick' the complete column under the cursor (i.e. the attribute under
1122 the cursor) from all currently fetched objects. These attributes
1122 the cursor) from all currently fetched objects. These attributes
1123 will be returned as a list.
1123 will be returned as a list.
1124 """
1124 """
1125 level = self.levels[-1]
1125 level = self.levels[-1]
1126 attr = level.displayattr[1]
1126 attr = level.displayattr[1]
1127 if attr is ipipe.noitem:
1127 if attr is ipipe.noitem:
1128 curses.beep()
1128 curses.beep()
1129 self.report(CommandError("no column under cursor"))
1129 self.report(CommandError("no column under cursor"))
1130 return
1130 return
1131 result = []
1131 result = []
1132 for cache in level.items:
1132 for cache in level.items:
1133 value = attr.value(cache.item)
1133 value = attr.value(cache.item)
1134 if value is not ipipe.noitem:
1134 if value is not ipipe.noitem:
1135 result.append(value)
1135 result.append(value)
1136 self.returnvalue = result
1136 self.returnvalue = result
1137 return True
1137 return True
1138
1138
1139 def cmd_pickmarked(self):
1139 def cmd_pickmarked(self):
1140 """
1140 """
1141 'Pick' marked objects. Marked objects will be returned as a list.
1141 'Pick' marked objects. Marked objects will be returned as a list.
1142 """
1142 """
1143 level = self.levels[-1]
1143 level = self.levels[-1]
1144 self.returnvalue = [cache.item for cache in level.items if cache.marked]
1144 self.returnvalue = [cache.item for cache in level.items if cache.marked]
1145 return True
1145 return True
1146
1146
1147 def cmd_pickmarkedattr(self):
1147 def cmd_pickmarkedattr(self):
1148 """
1148 """
1149 'Pick' the attribute under the cursor from all marked objects
1149 'Pick' the attribute under the cursor from all marked objects
1150 (This returns a list).
1150 (This returns a list).
1151 """
1151 """
1152
1152
1153 level = self.levels[-1]
1153 level = self.levels[-1]
1154 attr = level.displayattr[1]
1154 attr = level.displayattr[1]
1155 if attr is ipipe.noitem:
1155 if attr is ipipe.noitem:
1156 curses.beep()
1156 curses.beep()
1157 self.report(CommandError("no column under cursor"))
1157 self.report(CommandError("no column under cursor"))
1158 return
1158 return
1159 result = []
1159 result = []
1160 for cache in level.items:
1160 for cache in level.items:
1161 if cache.marked:
1161 if cache.marked:
1162 value = attr.value(cache.item)
1162 value = attr.value(cache.item)
1163 if value is not ipipe.noitem:
1163 if value is not ipipe.noitem:
1164 result.append(value)
1164 result.append(value)
1165 self.returnvalue = result
1165 self.returnvalue = result
1166 return True
1166 return True
1167
1167
1168 def cmd_pickinput(self):
1168 def cmd_pickinput(self):
1169 """
1169 """
1170 Use the object under the cursor (i.e. the row the cursor is on) as
1170 Use the object under the cursor (i.e. the row the cursor is on) as
1171 the next input line. This leaves the browser and puts the picked object
1171 the next input line. This leaves the browser and puts the picked object
1172 in the input.
1172 in the input.
1173 """
1173 """
1174 level = self.levels[-1]
1174 level = self.levels[-1]
1175 value = level.items[level.cury].item
1175 value = level.items[level.cury].item
1176 self.returnvalue = None
1176 self.returnvalue = None
1177 api = ipapi.get()
1177 api = ipapi.get()
1178 api.set_next_input(str(value))
1178 api.set_next_input(str(value))
1179 return True
1179 return True
1180
1180
1181 def cmd_pickinputattr(self):
1181 def cmd_pickinputattr(self):
1182 """
1182 """
1183 Use the attribute under the cursor i.e. the row/column the cursor is on)
1183 Use the attribute under the cursor i.e. the row/column the cursor is on)
1184 as the next input line. This leaves the browser and puts the picked
1184 as the next input line. This leaves the browser and puts the picked
1185 object in the input.
1185 object in the input.
1186 """
1186 """
1187 level = self.levels[-1]
1187 level = self.levels[-1]
1188 attr = level.displayattr[1]
1188 attr = level.displayattr[1]
1189 if attr is ipipe.noitem:
1189 if attr is ipipe.noitem:
1190 curses.beep()
1190 curses.beep()
1191 self.report(CommandError("no column under cursor"))
1191 self.report(CommandError("no column under cursor"))
1192 return
1192 return
1193 value = attr.value(level.items[level.cury].item)
1193 value = attr.value(level.items[level.cury].item)
1194 if value is ipipe.noitem:
1194 if value is ipipe.noitem:
1195 curses.beep()
1195 curses.beep()
1196 self.report(AttributeError(attr.name()))
1196 self.report(AttributeError(attr.name()))
1197 self.returnvalue = None
1197 self.returnvalue = None
1198 api = ipapi.get()
1198 api = ipapi.get()
1199 api.set_next_input(str(value))
1199 api.set_next_input(str(value))
1200 return True
1200 return True
1201
1201
1202 def cmd_markrange(self):
1202 def cmd_markrange(self):
1203 """
1203 """
1204 Mark all objects from the last marked object before the current cursor
1204 Mark all objects from the last marked object before the current cursor
1205 position to the cursor position.
1205 position to the cursor position.
1206 """
1206 """
1207 level = self.levels[-1]
1207 level = self.levels[-1]
1208 self.report("markrange")
1208 self.report("markrange")
1209 start = None
1209 start = None
1210 if level.items:
1210 if level.items:
1211 for i in xrange(level.cury, -1, -1):
1211 for i in xrange(level.cury, -1, -1):
1212 if level.items[i].marked:
1212 if level.items[i].marked:
1213 start = i
1213 start = i
1214 break
1214 break
1215 if start is None:
1215 if start is None:
1216 self.report(CommandError("no mark before cursor"))
1216 self.report(CommandError("no mark before cursor"))
1217 curses.beep()
1217 curses.beep()
1218 else:
1218 else:
1219 for i in xrange(start, level.cury+1):
1219 for i in xrange(start, level.cury+1):
1220 cache = level.items[i]
1220 cache = level.items[i]
1221 if not cache.marked:
1221 if not cache.marked:
1222 cache.marked = True
1222 cache.marked = True
1223 level.marked += 1
1223 level.marked += 1
1224
1224
1225 def cmd_enter(self):
1225 def cmd_enter(self):
1226 """
1226 """
1227 Enter the object under the cursor. (what this mean depends on the object
1227 Enter the object under the cursor. (what this mean depends on the object
1228 itself (i.e. how it implements iteration). This opens a new browser 'level'.
1228 itself (i.e. how it implements iteration). This opens a new browser 'level'.
1229 """
1229 """
1230 level = self.levels[-1]
1230 level = self.levels[-1]
1231 try:
1231 try:
1232 item = level.items[level.cury].item
1232 item = level.items[level.cury].item
1233 except IndexError:
1233 except IndexError:
1234 self.report(CommandError("No object"))
1234 self.report(CommandError("No object"))
1235 curses.beep()
1235 curses.beep()
1236 else:
1236 else:
1237 self.report("entering object...")
1237 self.report("entering object...")
1238 self.enter(item)
1238 self.enter(item)
1239
1239
1240 def cmd_leave(self):
1240 def cmd_leave(self):
1241 """
1241 """
1242 Leave the current browser level and go back to the previous one.
1242 Leave the current browser level and go back to the previous one.
1243 """
1243 """
1244 self.report("leave")
1244 self.report("leave")
1245 if len(self.levels) > 1:
1245 if len(self.levels) > 1:
1246 self._calcheaderlines(len(self.levels)-1)
1246 self._calcheaderlines(len(self.levels)-1)
1247 self.levels.pop(-1)
1247 self.levels.pop(-1)
1248 else:
1248 else:
1249 self.report(CommandError("This is the last level"))
1249 self.report(CommandError("This is the last level"))
1250 curses.beep()
1250 curses.beep()
1251
1251
1252 def cmd_enterattr(self):
1252 def cmd_enterattr(self):
1253 """
1253 """
1254 Enter the attribute under the cursor.
1254 Enter the attribute under the cursor.
1255 """
1255 """
1256 level = self.levels[-1]
1256 level = self.levels[-1]
1257 attr = level.displayattr[1]
1257 attr = level.displayattr[1]
1258 if attr is ipipe.noitem:
1258 if attr is ipipe.noitem:
1259 curses.beep()
1259 curses.beep()
1260 self.report(CommandError("no column under cursor"))
1260 self.report(CommandError("no column under cursor"))
1261 return
1261 return
1262 try:
1262 try:
1263 item = level.items[level.cury].item
1263 item = level.items[level.cury].item
1264 except IndexError:
1264 except IndexError:
1265 self.report(CommandError("No object"))
1265 self.report(CommandError("No object"))
1266 curses.beep()
1266 curses.beep()
1267 else:
1267 else:
1268 value = attr.value(item)
1268 value = attr.value(item)
1269 name = attr.name()
1269 name = attr.name()
1270 if value is ipipe.noitem:
1270 if value is ipipe.noitem:
1271 self.report(AttributeError(name))
1271 self.report(AttributeError(name))
1272 else:
1272 else:
1273 self.report("entering object attribute %s..." % name)
1273 self.report("entering object attribute %s..." % name)
1274 self.enter(value)
1274 self.enter(value)
1275
1275
1276 def cmd_detail(self):
1276 def cmd_detail(self):
1277 """
1277 """
1278 Show a detail view of the object under the cursor. This shows the
1278 Show a detail view of the object under the cursor. This shows the
1279 name, type, doc string and value of the object attributes (and it
1279 name, type, doc string and value of the object attributes (and it
1280 might show more attributes than in the list view, depending on
1280 might show more attributes than in the list view, depending on
1281 the object).
1281 the object).
1282 """
1282 """
1283 level = self.levels[-1]
1283 level = self.levels[-1]
1284 try:
1284 try:
1285 item = level.items[level.cury].item
1285 item = level.items[level.cury].item
1286 except IndexError:
1286 except IndexError:
1287 self.report(CommandError("No object"))
1287 self.report(CommandError("No object"))
1288 curses.beep()
1288 curses.beep()
1289 else:
1289 else:
1290 self.report("entering detail view for object...")
1290 self.report("entering detail view for object...")
1291 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1291 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1292 self.enter(attrs)
1292 self.enter(attrs)
1293
1293
1294 def cmd_detailattr(self):
1294 def cmd_detailattr(self):
1295 """
1295 """
1296 Show a detail view of the attribute under the cursor.
1296 Show a detail view of the attribute under the cursor.
1297 """
1297 """
1298 level = self.levels[-1]
1298 level = self.levels[-1]
1299 attr = level.displayattr[1]
1299 attr = level.displayattr[1]
1300 if attr is ipipe.noitem:
1300 if attr is ipipe.noitem:
1301 curses.beep()
1301 curses.beep()
1302 self.report(CommandError("no attribute"))
1302 self.report(CommandError("no attribute"))
1303 return
1303 return
1304 try:
1304 try:
1305 item = level.items[level.cury].item
1305 item = level.items[level.cury].item
1306 except IndexError:
1306 except IndexError:
1307 self.report(CommandError("No object"))
1307 self.report(CommandError("No object"))
1308 curses.beep()
1308 curses.beep()
1309 else:
1309 else:
1310 try:
1310 try:
1311 item = attr.value(item)
1311 item = attr.value(item)
1312 except (KeyboardInterrupt, SystemExit):
1312 except (KeyboardInterrupt, SystemExit):
1313 raise
1313 raise
1314 except Exception, exc:
1314 except Exception, exc:
1315 self.report(exc)
1315 self.report(exc)
1316 else:
1316 else:
1317 self.report("entering detail view for attribute %s..." % attr.name())
1317 self.report("entering detail view for attribute %s..." % attr.name())
1318 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1318 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1319 self.enter(attrs)
1319 self.enter(attrs)
1320
1320
1321 def cmd_tooglemark(self):
1321 def cmd_tooglemark(self):
1322 """
1322 """
1323 Mark/unmark the object under the cursor. Marked objects have a '!'
1323 Mark/unmark the object under the cursor. Marked objects have a '!'
1324 after the row number).
1324 after the row number).
1325 """
1325 """
1326 level = self.levels[-1]
1326 level = self.levels[-1]
1327 self.report("toggle mark")
1327 self.report("toggle mark")
1328 try:
1328 try:
1329 item = level.items[level.cury]
1329 item = level.items[level.cury]
1330 except IndexError: # no items?
1330 except IndexError: # no items?
1331 pass
1331 pass
1332 else:
1332 else:
1333 if item.marked:
1333 if item.marked:
1334 item.marked = False
1334 item.marked = False
1335 level.marked -= 1
1335 level.marked -= 1
1336 else:
1336 else:
1337 item.marked = True
1337 item.marked = True
1338 level.marked += 1
1338 level.marked += 1
1339
1339
1340 def cmd_sortattrasc(self):
1340 def cmd_sortattrasc(self):
1341 """
1341 """
1342 Sort the objects (in ascending order) using the attribute under
1342 Sort the objects (in ascending order) using the attribute under
1343 the cursor as the sort key.
1343 the cursor as the sort key.
1344 """
1344 """
1345 level = self.levels[-1]
1345 level = self.levels[-1]
1346 attr = level.displayattr[1]
1346 attr = level.displayattr[1]
1347 if attr is ipipe.noitem:
1347 if attr is ipipe.noitem:
1348 curses.beep()
1348 curses.beep()
1349 self.report(CommandError("no column under cursor"))
1349 self.report(CommandError("no column under cursor"))
1350 return
1350 return
1351 self.report("sort by %s (ascending)" % attr.name())
1351 self.report("sort by %s (ascending)" % attr.name())
1352 def key(item):
1352 def key(item):
1353 try:
1353 try:
1354 return attr.value(item)
1354 return attr.value(item)
1355 except (KeyboardInterrupt, SystemExit):
1355 except (KeyboardInterrupt, SystemExit):
1356 raise
1356 raise
1357 except Exception:
1357 except Exception:
1358 return None
1358 return None
1359 level.sort(key)
1359 level.sort(key)
1360
1360
1361 def cmd_sortattrdesc(self):
1361 def cmd_sortattrdesc(self):
1362 """
1362 """
1363 Sort the objects (in descending order) using the attribute under
1363 Sort the objects (in descending order) using the attribute under
1364 the cursor as the sort key.
1364 the cursor as the sort key.
1365 """
1365 """
1366 level = self.levels[-1]
1366 level = self.levels[-1]
1367 attr = level.displayattr[1]
1367 attr = level.displayattr[1]
1368 if attr is ipipe.noitem:
1368 if attr is ipipe.noitem:
1369 curses.beep()
1369 curses.beep()
1370 self.report(CommandError("no column under cursor"))
1370 self.report(CommandError("no column under cursor"))
1371 return
1371 return
1372 self.report("sort by %s (descending)" % attr.name())
1372 self.report("sort by %s (descending)" % attr.name())
1373 def key(item):
1373 def key(item):
1374 try:
1374 try:
1375 return attr.value(item)
1375 return attr.value(item)
1376 except (KeyboardInterrupt, SystemExit):
1376 except (KeyboardInterrupt, SystemExit):
1377 raise
1377 raise
1378 except Exception:
1378 except Exception:
1379 return None
1379 return None
1380 level.sort(key, reverse=True)
1380 level.sort(key, reverse=True)
1381
1381
1382 def cmd_hideattr(self):
1382 def cmd_hideattr(self):
1383 """
1383 """
1384 Hide the attribute under the cursor.
1384 Hide the attribute under the cursor.
1385 """
1385 """
1386 level = self.levels[-1]
1386 level = self.levels[-1]
1387 if level.displayattr[0] is None:
1387 if level.displayattr[0] is None:
1388 self.beep()
1388 self.beep()
1389 else:
1389 else:
1390 self.report("hideattr")
1390 self.report("hideattr")
1391 level.hiddenattrs.add(level.displayattr[1])
1391 level.hiddenattrs.add(level.displayattr[1])
1392 level.moveto(level.curx, level.cury, refresh=True)
1392 level.moveto(level.curx, level.cury, refresh=True)
1393
1393
1394 def cmd_unhideattrs(self):
1394 def cmd_unhideattrs(self):
1395 """
1395 """
1396 Make all attributes visible again.
1396 Make all attributes visible again.
1397 """
1397 """
1398 level = self.levels[-1]
1398 level = self.levels[-1]
1399 self.report("unhideattrs")
1399 self.report("unhideattrs")
1400 level.hiddenattrs.clear()
1400 level.hiddenattrs.clear()
1401 level.moveto(level.curx, level.cury, refresh=True)
1401 level.moveto(level.curx, level.cury, refresh=True)
1402
1402
1403 def cmd_goto(self):
1403 def cmd_goto(self):
1404 """
1404 """
1405 Jump to a row. The row number can be entered at the
1405 Jump to a row. The row number can be entered at the
1406 bottom of the screen.
1406 bottom of the screen.
1407 """
1407 """
1408 self.startkeyboardinput("goto")
1408 self.startkeyboardinput("goto")
1409
1409
1410 def cmd_find(self):
1410 def cmd_find(self):
1411 """
1411 """
1412 Search forward for a row. The search condition can be entered at the
1412 Search forward for a row. The search condition can be entered at the
1413 bottom of the screen.
1413 bottom of the screen.
1414 """
1414 """
1415 self.startkeyboardinput("find")
1415 self.startkeyboardinput("find")
1416
1416
1417 def cmd_findbackwards(self):
1417 def cmd_findbackwards(self):
1418 """
1418 """
1419 Search backward for a row. The search condition can be entered at the
1419 Search backward for a row. The search condition can be entered at the
1420 bottom of the screen.
1420 bottom of the screen.
1421 """
1421 """
1422 self.startkeyboardinput("findbackwards")
1422 self.startkeyboardinput("findbackwards")
1423
1423
1424 def cmd_refresh(self):
1424 def cmd_refresh(self):
1425 """
1425 """
1426 Refreshes the display by restarting the iterator.
1426 Refreshes the display by restarting the iterator.
1427 """
1427 """
1428 level = self.levels[-1]
1428 level = self.levels[-1]
1429 self.report("refresh")
1429 self.report("refresh")
1430 level.refresh()
1430 level.refresh()
1431
1431
1432 def cmd_refreshfind(self):
1432 def cmd_refreshfind(self):
1433 """
1433 """
1434 Refreshes the display by restarting the iterator and goes back to the
1434 Refreshes the display by restarting the iterator and goes back to the
1435 same object the cursor was on before restarting (if this object can't be
1435 same object the cursor was on before restarting (if this object can't be
1436 found the cursor jumps back to the first object).
1436 found the cursor jumps back to the first object).
1437 """
1437 """
1438 level = self.levels[-1]
1438 level = self.levels[-1]
1439 self.report("refreshfind")
1439 self.report("refreshfind")
1440 level.refreshfind()
1440 level.refreshfind()
1441
1441
1442 def cmd_help(self):
1442 def cmd_help(self):
1443 """
1443 """
1444 Opens the help screen as a new browser level, describing keyboard
1444 Opens the help screen as a new browser level, describing keyboard
1445 shortcuts.
1445 shortcuts.
1446 """
1446 """
1447 for level in self.levels:
1447 for level in self.levels:
1448 if isinstance(level.input, _BrowserHelp):
1448 if isinstance(level.input, _BrowserHelp):
1449 curses.beep()
1449 curses.beep()
1450 self.report(CommandError("help already active"))
1450 self.report(CommandError("help already active"))
1451 return
1451 return
1452
1452
1453 self.enter(_BrowserHelp(self))
1453 self.enter(_BrowserHelp(self))
1454
1454
1455 def cmd_quit(self):
1455 def cmd_quit(self):
1456 """
1456 """
1457 Quit the browser and return to the IPython prompt.
1457 Quit the browser and return to the IPython prompt.
1458 """
1458 """
1459 self.returnvalue = None
1459 self.returnvalue = None
1460 return True
1460 return True
1461
1461
1462 def sigwinchhandler(self, signal, frame):
1462 def sigwinchhandler(self, signal, frame):
1463 self.resized = True
1463 self.resized = True
1464
1464
1465 def _dodisplay(self, scr):
1465 def _dodisplay(self, scr):
1466 """
1466 """
1467 This method is the workhorse of the browser. It handles screen
1467 This method is the workhorse of the browser. It handles screen
1468 drawing and the keyboard.
1468 drawing and the keyboard.
1469 """
1469 """
1470 self.scr = scr
1470 self.scr = scr
1471 curses.halfdelay(1)
1471 curses.halfdelay(1)
1472 footery = 2
1472 footery = 2
1473
1473
1474 keys = []
1474 keys = []
1475 for cmd in ("quit", "help"):
1475 for cmd in ("quit", "help"):
1476 key = self.keymap.findkey(cmd, None)
1476 key = self.keymap.findkey(cmd, None)
1477 if key is not None:
1477 if key is not None:
1478 keys.append("%s=%s" % (self.keylabel(key), cmd))
1478 keys.append("%s=%s" % (self.keylabel(key), cmd))
1479 helpmsg = " | %s" % " ".join(keys)
1479 helpmsg = " | %s" % " ".join(keys)
1480
1480
1481 scr.clear()
1481 scr.clear()
1482 msg = "Fetching first batch of objects..."
1482 msg = "Fetching first batch of objects..."
1483 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1483 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1484 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1484 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1485 scr.refresh()
1485 scr.refresh()
1486
1486
1487 lastc = -1
1487 lastc = -1
1488
1488
1489 self.levels = []
1489 self.levels = []
1490 # enter the first level
1490 # enter the first level
1491 self.enter(self.input, *self.attrs)
1491 self.enter(self.input, *self.attrs)
1492
1492
1493 self._calcheaderlines(None)
1493 self._calcheaderlines(None)
1494
1494
1495 while True:
1495 while True:
1496 level = self.levels[-1]
1496 level = self.levels[-1]
1497 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1497 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1498 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1498 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1499
1499
1500 # Paint object header
1500 # Paint object header
1501 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1501 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1502 lv = self.levels[i]
1502 lv = self.levels[i]
1503 posx = 0
1503 posx = 0
1504 posy = i-self._firstheaderline
1504 posy = i-self._firstheaderline
1505 endx = self.scrsizex
1505 endx = self.scrsizex
1506 if i: # not the first level
1506 if i: # not the first level
1507 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1507 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1508 if not self.levels[i-1].exhausted:
1508 if not self.levels[i-1].exhausted:
1509 msg += "+"
1509 msg += "+"
1510 msg += ") "
1510 msg += ") "
1511 endx -= len(msg)+1
1511 endx -= len(msg)+1
1512 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1512 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1513 for (style, text) in lv.header:
1513 for (style, text) in lv.header:
1514 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1514 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1515 if posx >= endx:
1515 if posx >= endx:
1516 break
1516 break
1517 if i:
1517 if i:
1518 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1518 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1519 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1519 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1520
1520
1521 if not level.items:
1521 if not level.items:
1522 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1522 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1523 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1523 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1524 scr.clrtobot()
1524 scr.clrtobot()
1525 else:
1525 else:
1526 # Paint column headers
1526 # Paint column headers
1527 scr.move(self._headerlines, 0)
1527 scr.move(self._headerlines, 0)
1528 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1528 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1529 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1529 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1530 begx = level.numbersizex+3
1530 begx = level.numbersizex+3
1531 posx = begx-level.datastartx
1531 posx = begx-level.datastartx
1532 for attr in level.displayattrs:
1532 for attr in level.displayattrs:
1533 attrname = attr.name()
1533 attrname = attr.name()
1534 cwidth = level.colwidths[attr]
1534 cwidth = level.colwidths[attr]
1535 header = attrname.ljust(cwidth)
1535 header = attrname.ljust(cwidth)
1536 if attr is level.displayattr[1]:
1536 if attr is level.displayattr[1]:
1537 style = self.style_colheaderhere
1537 style = self.style_colheaderhere
1538 else:
1538 else:
1539 style = self.style_colheader
1539 style = self.style_colheader
1540 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1540 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1541 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1541 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1542 if posx >= self.scrsizex:
1542 if posx >= self.scrsizex:
1543 break
1543 break
1544 else:
1544 else:
1545 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1545 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1546
1546
1547 # Paint rows
1547 # Paint rows
1548 posy = self._headerlines+1+level.datastarty
1548 posy = self._headerlines+1+level.datastarty
1549 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1549 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1550 cache = level.items[i]
1550 cache = level.items[i]
1551 if i == level.cury:
1551 if i == level.cury:
1552 style = self.style_numberhere
1552 style = self.style_numberhere
1553 else:
1553 else:
1554 style = self.style_number
1554 style = self.style_number
1555
1555
1556 posy = self._headerlines+1+i-level.datastarty
1556 posy = self._headerlines+1+i-level.datastarty
1557 posx = begx-level.datastartx
1557 posx = begx-level.datastartx
1558
1558
1559 scr.move(posy, 0)
1559 scr.move(posy, 0)
1560 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1560 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1561 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1561 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1562
1562
1563 for attrname in level.displayattrs:
1563 for attrname in level.displayattrs:
1564 cwidth = level.colwidths[attrname]
1564 cwidth = level.colwidths[attrname]
1565 try:
1565 try:
1566 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1566 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1567 except KeyError:
1567 except KeyError:
1568 align = 2
1568 align = 2
1569 style = astyle.style_nodata
1569 style = astyle.style_nodata
1570 if i == level.cury:
1570 if i == level.cury:
1571 style = self.getstylehere(style)
1571 style = self.getstylehere(style)
1572 padstyle = self.style_datapad
1572 padstyle = self.style_datapad
1573 sepstyle = self.style_sep
1573 sepstyle = self.style_sep
1574 if i == level.cury:
1574 if i == level.cury:
1575 padstyle = self.getstylehere(padstyle)
1575 padstyle = self.getstylehere(padstyle)
1576 sepstyle = self.getstylehere(sepstyle)
1576 sepstyle = self.getstylehere(sepstyle)
1577 if align == 2:
1577 if align == 2:
1578 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1578 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1579 else:
1579 else:
1580 if align == 1:
1580 if align == 1:
1581 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1581 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1582 elif align == 0:
1582 elif align == 0:
1583 pad1 = (cwidth-length)//2
1583 pad1 = (cwidth-length)//2
1584 pad2 = cwidth-length-len(pad1)
1584 pad2 = cwidth-length-len(pad1)
1585 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1585 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1586 for (style, text) in parts:
1586 for (style, text) in parts:
1587 if i == level.cury:
1587 if i == level.cury:
1588 style = self.getstylehere(style)
1588 style = self.getstylehere(style)
1589 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1589 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1590 if posx >= self.scrsizex:
1590 if posx >= self.scrsizex:
1591 break
1591 break
1592 if align == -1:
1592 if align == -1:
1593 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1593 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1594 elif align == 0:
1594 elif align == 0:
1595 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1595 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1596 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1596 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1597 else:
1597 else:
1598 scr.clrtoeol()
1598 scr.clrtoeol()
1599
1599
1600 # Add blank row headers for the rest of the screen
1600 # Add blank row headers for the rest of the screen
1601 for posy in xrange(posy+1, self.scrsizey-2):
1601 for posy in xrange(posy+1, self.scrsizey-2):
1602 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1602 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1603 scr.clrtoeol()
1603 scr.clrtoeol()
1604
1604
1605 posy = self.scrsizey-footery
1605 posy = self.scrsizey-footery
1606 # Display footer
1606 # Display footer
1607 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1607 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1608
1608
1609 if level.exhausted:
1609 if level.exhausted:
1610 flag = ""
1610 flag = ""
1611 else:
1611 else:
1612 flag = "+"
1612 flag = "+"
1613
1613
1614 endx = self.scrsizex-len(helpmsg)-1
1614 endx = self.scrsizex-len(helpmsg)-1
1615 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1615 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1616
1616
1617 posx = 0
1617 posx = 0
1618 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1618 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1619 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1619 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1620 try:
1620 try:
1621 item = level.items[level.cury].item
1621 item = level.items[level.cury].item
1622 except IndexError: # empty
1622 except IndexError: # empty
1623 pass
1623 pass
1624 else:
1624 else:
1625 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1625 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1626 if not isinstance(nostyle, int):
1626 if not isinstance(nostyle, int):
1627 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1627 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1628 if posx >= endx:
1628 if posx >= endx:
1629 break
1629 break
1630
1630
1631 attrstyle = [(astyle.style_default, "no attribute")]
1631 attrstyle = [(astyle.style_default, "no attribute")]
1632 attr = level.displayattr[1]
1632 attr = level.displayattr[1]
1633 if attr is not ipipe.noitem and not isinstance(attr, ipipe.SelfDescriptor):
1633 if attr is not ipipe.noitem and not isinstance(attr, ipipe.SelfDescriptor):
1634 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1634 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1635 posx += self.addstr(posy, posx, 0, endx, attr.name(), self.style_footer)
1635 posx += self.addstr(posy, posx, 0, endx, attr.name(), self.style_footer)
1636 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1636 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1637 try:
1637 try:
1638 value = attr.value(item)
1638 value = attr.value(item)
1639 except (SystemExit, KeyboardInterrupt):
1639 except (SystemExit, KeyboardInterrupt):
1640 raise
1640 raise
1641 except Exception, exc:
1641 except Exception, exc:
1642 value = exc
1642 value = exc
1643 if value is not ipipe.noitem:
1643 if value is not ipipe.noitem:
1644 attrstyle = ipipe.xrepr(value, "footer")
1644 attrstyle = ipipe.xrepr(value, "footer")
1645 for (nostyle, text) in attrstyle:
1645 for (nostyle, text) in attrstyle:
1646 if not isinstance(nostyle, int):
1646 if not isinstance(nostyle, int):
1647 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1647 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1648 if posx >= endx:
1648 if posx >= endx:
1649 break
1649 break
1650
1650
1651 try:
1651 try:
1652 # Display input prompt
1652 # Display input prompt
1653 if self.mode in self.prompts:
1653 if self.mode in self.prompts:
1654 history = self.prompts[self.mode]
1654 history = self.prompts[self.mode]
1655 posx = 0
1655 posx = 0
1656 posy = self.scrsizey-1
1656 posy = self.scrsizey-1
1657 posx += self.addstr(posy, posx, 0, endx, history.prompt, astyle.style_default)
1657 posx += self.addstr(posy, posx, 0, endx, history.prompt, astyle.style_default)
1658 posx += self.addstr(posy, posx, 0, endx, " [", astyle.style_default)
1658 posx += self.addstr(posy, posx, 0, endx, " [", astyle.style_default)
1659 if history.cury==-1:
1659 if history.cury==-1:
1660 text = "new"
1660 text = "new"
1661 else:
1661 else:
1662 text = str(history.cury+1)
1662 text = str(history.cury+1)
1663 posx += self.addstr(posy, posx, 0, endx, text, astyle.style_type_number)
1663 posx += self.addstr(posy, posx, 0, endx, text, astyle.style_type_number)
1664 if history.history:
1664 if history.history:
1665 posx += self.addstr(posy, posx, 0, endx, "/", astyle.style_default)
1665 posx += self.addstr(posy, posx, 0, endx, "/", astyle.style_default)
1666 posx += self.addstr(posy, posx, 0, endx, str(len(history.history)), astyle.style_type_number)
1666 posx += self.addstr(posy, posx, 0, endx, str(len(history.history)), astyle.style_type_number)
1667 posx += self.addstr(posy, posx, 0, endx, "]: ", astyle.style_default)
1667 posx += self.addstr(posy, posx, 0, endx, "]: ", astyle.style_default)
1668 inputstartx = posx
1668 inputstartx = posx
1669 posx += self.addstr(posy, posx, 0, endx, history.input, astyle.style_default)
1669 posx += self.addstr(posy, posx, 0, endx, history.input, astyle.style_default)
1670 # Display report
1670 # Display report
1671 else:
1671 else:
1672 if self._report is not None:
1672 if self._report is not None:
1673 if isinstance(self._report, Exception):
1673 if isinstance(self._report, Exception):
1674 style = self.getstyle(astyle.style_error)
1674 style = self.getstyle(astyle.style_error)
1675 if self._report.__class__.__module__ == "exceptions":
1675 if self._report.__class__.__module__ == "exceptions":
1676 msg = "%s: %s" % \
1676 msg = "%s: %s" % \
1677 (self._report.__class__.__name__, self._report)
1677 (self._report.__class__.__name__, self._report)
1678 else:
1678 else:
1679 msg = "%s.%s: %s" % \
1679 msg = "%s.%s: %s" % \
1680 (self._report.__class__.__module__,
1680 (self._report.__class__.__module__,
1681 self._report.__class__.__name__, self._report)
1681 self._report.__class__.__name__, self._report)
1682 else:
1682 else:
1683 style = self.getstyle(self.style_report)
1683 style = self.getstyle(self.style_report)
1684 msg = self._report
1684 msg = self._report
1685 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1685 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1686 self._report = None
1686 self._report = None
1687 else:
1687 else:
1688 scr.move(self.scrsizey-1, 0)
1688 scr.move(self.scrsizey-1, 0)
1689 except curses.error:
1689 except curses.error:
1690 # Protect against errors from writing to the last line
1690 # Protect against errors from writing to the last line
1691 pass
1691 pass
1692 scr.clrtoeol()
1692 scr.clrtoeol()
1693
1693
1694 # Position cursor
1694 # Position cursor
1695 if self.mode in self.prompts:
1695 if self.mode in self.prompts:
1696 history = self.prompts[self.mode]
1696 history = self.prompts[self.mode]
1697 scr.move(self.scrsizey-1, inputstartx+history.curx)
1697 scr.move(self.scrsizey-1, inputstartx+history.curx)
1698 else:
1698 else:
1699 scr.move(
1699 scr.move(
1700 1+self._headerlines+level.cury-level.datastarty,
1700 1+self._headerlines+level.cury-level.datastarty,
1701 level.numbersizex+3+level.curx-level.datastartx
1701 level.numbersizex+3+level.curx-level.datastartx
1702 )
1702 )
1703 scr.refresh()
1703 scr.refresh()
1704
1704
1705 # Check keyboard
1705 # Check keyboard
1706 while True:
1706 while True:
1707 c = scr.getch()
1707 c = scr.getch()
1708 if self.resized:
1708 if self.resized:
1709 size = fcntl.ioctl(0, tty.TIOCGWINSZ, "12345678")
1709 size = fcntl.ioctl(0, tty.TIOCGWINSZ, "12345678")
1710 size = struct.unpack("4H", size)
1710 size = struct.unpack("4H", size)
1711 oldsize = scr.getmaxyx()
1711 oldsize = scr.getmaxyx()
1712 scr.erase()
1712 scr.erase()
1713 curses.resize_term(size[0], size[1])
1713 curses.resize_term(size[0], size[1])
1714 newsize = scr.getmaxyx()
1714 newsize = scr.getmaxyx()
1715 scr.erase()
1715 scr.erase()
1716 for l in self.levels:
1716 for l in self.levels:
1717 l.mainsizey += newsize[0]-oldsize[0]
1717 l.mainsizey += newsize[0]-oldsize[0]
1718 l.moveto(l.curx, l.cury, refresh=True)
1718 l.moveto(l.curx, l.cury, refresh=True)
1719 scr.refresh()
1719 scr.refresh()
1720 self.resized = False
1720 self.resized = False
1721 break # Redisplay
1721 break # Redisplay
1722 if self.mode in self.prompts:
1722 if self.mode in self.prompts:
1723 if self.prompts[self.mode].handlekey(self, c):
1723 if self.prompts[self.mode].handlekey(self, c):
1724 break # Redisplay
1724 break # Redisplay
1725 else:
1725 else:
1726 # if no key is pressed slow down and beep again
1726 # if no key is pressed slow down and beep again
1727 if c == -1:
1727 if c == -1:
1728 self.stepx = 1.
1728 self.stepx = 1.
1729 self.stepy = 1.
1729 self.stepy = 1.
1730 self._dobeep = True
1730 self._dobeep = True
1731 else:
1731 else:
1732 # if a different key was pressed slow down and beep too
1732 # if a different key was pressed slow down and beep too
1733 if c != lastc:
1733 if c != lastc:
1734 lastc = c
1734 lastc = c
1735 self.stepx = 1.
1735 self.stepx = 1.
1736 self.stepy = 1.
1736 self.stepy = 1.
1737 self._dobeep = True
1737 self._dobeep = True
1738 cmdname = self.keymap.get(c, None)
1738 cmdname = self.keymap.get(c, None)
1739 if cmdname is None:
1739 if cmdname is None:
1740 self.report(
1740 self.report(
1741 UnassignedKeyError("Unassigned key %s" %
1741 UnassignedKeyError("Unassigned key %s" %
1742 self.keylabel(c)))
1742 self.keylabel(c)))
1743 else:
1743 else:
1744 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1744 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1745 if cmdfunc is None:
1745 if cmdfunc is None:
1746 self.report(
1746 self.report(
1747 UnknownCommandError("Unknown command %r" %
1747 UnknownCommandError("Unknown command %r" %
1748 (cmdname,)))
1748 (cmdname,)))
1749 elif cmdfunc():
1749 elif cmdfunc():
1750 returnvalue = self.returnvalue
1750 returnvalue = self.returnvalue
1751 self.returnvalue = None
1751 self.returnvalue = None
1752 return returnvalue
1752 return returnvalue
1753 self.stepx = self.nextstepx(self.stepx)
1753 self.stepx = self.nextstepx(self.stepx)
1754 self.stepy = self.nextstepy(self.stepy)
1754 self.stepy = self.nextstepy(self.stepy)
1755 curses.flushinp() # get rid of type ahead
1755 curses.flushinp() # get rid of type ahead
1756 break # Redisplay
1756 break # Redisplay
1757 self.scr = None
1757 self.scr = None
1758
1758
1759 def display(self):
1759 def display(self):
1760 if hasattr(curses, "resize_term"):
1760 if hasattr(curses, "resize_term"):
1761 oldhandler = signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1761 oldhandler = signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1762 try:
1762 try:
1763 return curses.wrapper(self._dodisplay)
1763 return curses.wrapper(self._dodisplay)
1764 finally:
1764 finally:
1765 signal.signal(signal.SIGWINCH, oldhandler)
1765 signal.signal(signal.SIGWINCH, oldhandler)
1766 else:
1766 else:
1767 return curses.wrapper(self._dodisplay)
1767 return curses.wrapper(self._dodisplay)
@@ -1,300 +1,286 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython
4 This module provides a main entry point to a user script to test IPython
5 itself from the command line. There are two ways of running this script:
5 itself from the command line. There are two ways of running this script:
6
6
7 1. With the syntax `iptest all`. This runs our entire test suite by
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 calling this script (with different arguments) or trial recursively. This
8 calling this script (with different arguments) or trial recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
13 plugins loaded.
14
14
15 For now, this script requires that both nose and twisted are installed. This
15 For now, this script requires that both nose and twisted are installed. This
16 will change in the future.
16 will change in the future.
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Module imports
20 # Module imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import os
23 import os
24 import os.path as path
24 import os.path as path
25 import sys
25 import sys
26 import subprocess
26 import subprocess
27 import time
27 import time
28 import warnings
28 import warnings
29
29
30 import nose.plugins.builtin
30 import nose.plugins.builtin
31 from nose.core import TestProgram
31 from nose.core import TestProgram
32
32
33 from IPython.utils.platutils import find_cmd
33 from IPython.utils.platutils import find_cmd
34 from IPython.testing.plugin.ipdoctest import IPythonDoctest
34 from IPython.testing.plugin.ipdoctest import IPythonDoctest
35
35
36 pjoin = path.join
36 pjoin = path.join
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Logic for skipping doctests
39 # Logic for skipping doctests
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 def test_for(mod):
42 def test_for(mod):
43 """Test to see if mod is importable."""
43 """Test to see if mod is importable."""
44 try:
44 try:
45 __import__(mod)
45 __import__(mod)
46 except ImportError:
46 except ImportError:
47 return False
47 return False
48 else:
48 else:
49 return True
49 return True
50
50
51 have_curses = test_for('_curses')
51 have_curses = test_for('_curses')
52 have_wx = test_for('wx')
52 have_wx = test_for('wx')
53 have_zi = test_for('zope.interface')
53 have_zi = test_for('zope.interface')
54 have_twisted = test_for('twisted')
54 have_twisted = test_for('twisted')
55 have_foolscap = test_for('foolscap')
55 have_foolscap = test_for('foolscap')
56 have_objc = test_for('objc')
56 have_objc = test_for('objc')
57 have_pexpect = test_for('pexpect')
57 have_pexpect = test_for('pexpect')
58 have_gtk = test_for('gtk')
59 have_gobject = test_for('gobject')
60
61
62 def make_exclude():
63
64 # For the IPythonDoctest plugin, we need to exclude certain patterns that cause
65 # testing problems. We should strive to minimize the number of skipped
66 # modules, since this means untested code. As the testing machinery
67 # solidifies, this list should eventually become empty.
68 EXCLUDE = [pjoin('IPython', 'external'),
69 pjoin('IPython', 'frontend', 'process', 'winprocess.py'),
70 pjoin('IPython_doctest_plugin'),
71 pjoin('IPython', 'extensions', 'ipy_'),
72 pjoin('IPython', 'extensions', 'clearcmd'),
73 pjoin('IPython', 'extensions', 'PhysicalQInteractive'),
74 pjoin('IPython', 'extensions', 'scitedirector'),
75 pjoin('IPython', 'extensions', 'numeric_formats'),
76 pjoin('IPython', 'testing', 'attic'),
77 pjoin('IPython', 'testing', 'tools'),
78 pjoin('IPython', 'testing', 'mkdoctests'),
79 pjoin('IPython', 'lib', 'inputhook')
80 ]
81
82 if not have_wx:
83 EXCLUDE.append(pjoin('IPython', 'extensions', 'igrid'))
84 EXCLUDE.append(pjoin('IPython', 'gui'))
85 EXCLUDE.append(pjoin('IPython', 'frontend', 'wx'))
86 EXCLUDE.append(pjoin('IPython', 'lib', 'inputhookwx'))
87
88 if not have_gtk or not have_gobject:
89 EXCLUDE.append(pjoin('IPython', 'lib', 'inputhookgtk'))
90
91 if not have_objc:
92 EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa'))
93
94 if not have_curses:
95 EXCLUDE.append(pjoin('IPython', 'extensions', 'ibrowse'))
96
97 if not sys.platform == 'win32':
98 EXCLUDE.append(pjoin('IPython', 'utils', 'platutils_win32'))
99
100 # These have to be skipped on win32 because the use echo, rm, cd, etc.
101 # See ticket https://bugs.launchpad.net/bugs/366982
102 if sys.platform == 'win32':
103 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip'))
104 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample'))
105
106 if not os.name == 'posix':
107 EXCLUDE.append(pjoin('IPython', 'utils', 'platutils_posix'))
58
108
59 # For the IPythonDoctest plugin, we need to exclude certain patterns that cause
109 if not have_pexpect:
60 # testing problems. We should strive to minimize the number of skipped
110 EXCLUDE.append(pjoin('IPython', 'scripts', 'irunner'))
61 # modules, since this means untested code. As the testing machinery
62 # solidifies, this list should eventually become empty.
63 EXCLUDE = [pjoin('IPython', 'external'),
64 pjoin('IPython', 'frontend', 'process', 'winprocess.py'),
65 pjoin('IPython_doctest_plugin'),
66 pjoin('IPython', 'Gnuplot'),
67 pjoin('IPython', 'extensions', 'ipy_'),
68 pjoin('IPython', 'extensions', 'clearcmd'),
69 pjoin('IPython', 'extensions', 'PhysicalQInteractive'),
70 pjoin('IPython', 'extensions', 'scitedirector'),
71 pjoin('IPython', 'extensions', 'numeric_formats'),
72 pjoin('IPython', 'testing', 'attic'),
73 pjoin('IPython', 'testing', 'tutils'),
74 pjoin('IPython', 'testing', 'tools'),
75 pjoin('IPython', 'testing', 'mkdoctests')
76 ]
77
78 if not have_wx:
79 EXCLUDE.append(pjoin('IPython', 'extensions', 'igrid'))
80 EXCLUDE.append(pjoin('IPython', 'gui'))
81 EXCLUDE.append(pjoin('IPython', 'frontend', 'wx'))
82
83 if not have_objc:
84 EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa'))
85
86 if not have_curses:
87 EXCLUDE.append(pjoin('IPython', 'extensions', 'ibrowse'))
88
89 if not sys.platform == 'win32':
90 EXCLUDE.append(pjoin('IPython', 'platutils_win32'))
91
92 # These have to be skipped on win32 because the use echo, rm, cd, etc.
93 # See ticket https://bugs.launchpad.net/bugs/366982
94 if sys.platform == 'win32':
95 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip'))
96 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample'))
97
98 if not os.name == 'posix':
99 EXCLUDE.append(pjoin('IPython', 'platutils_posix'))
100
101 if not have_pexpect:
102 EXCLUDE.append(pjoin('IPython', 'lib', 'irunner'))
103
104 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
105 if sys.platform == 'win32':
106 EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE]
107
111
112 # Skip shell always because of a bug in FakeModule.
113 EXCLUDE.append(pjoin('IPython', 'core', 'shell'))
114
115 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
116 if sys.platform == 'win32':
117 EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE]
118
119 return EXCLUDE
108
120
109 #-----------------------------------------------------------------------------
121 #-----------------------------------------------------------------------------
110 # Functions and classes
122 # Functions and classes
111 #-----------------------------------------------------------------------------
123 #-----------------------------------------------------------------------------
112
124
113 def run_iptest():
125 def run_iptest():
114 """Run the IPython test suite using nose.
126 """Run the IPython test suite using nose.
115
127
116 This function is called when this script is **not** called with the form
128 This function is called when this script is **not** called with the form
117 `iptest all`. It simply calls nose with appropriate command line flags
129 `iptest all`. It simply calls nose with appropriate command line flags
118 and accepts all of the standard nose arguments.
130 and accepts all of the standard nose arguments.
119 """
131 """
120
132
121 warnings.filterwarnings('ignore',
133 warnings.filterwarnings('ignore',
122 'This will be removed soon. Use IPython.testing.util instead')
134 'This will be removed soon. Use IPython.testing.util instead')
123
135
124 argv = sys.argv + [
136 argv = sys.argv + [
125 # Loading ipdoctest causes problems with Twisted.
137 # Loading ipdoctest causes problems with Twisted.
126 # I am removing this as a temporary fix to get the
138 # I am removing this as a temporary fix to get the
127 # test suite back into working shape. Our nose
139 # test suite back into working shape. Our nose
128 # plugin needs to be gone through with a fine
140 # plugin needs to be gone through with a fine
129 # toothed comb to find what is causing the problem.
141 # toothed comb to find what is causing the problem.
130 '--with-ipdoctest',
142 '--with-ipdoctest',
131 '--ipdoctest-tests','--ipdoctest-extension=txt',
143 '--ipdoctest-tests','--ipdoctest-extension=txt',
132 '--detailed-errors',
144 '--detailed-errors',
133
145
134 # We add --exe because of setuptools' imbecility (it
146 # We add --exe because of setuptools' imbecility (it
135 # blindly does chmod +x on ALL files). Nose does the
147 # blindly does chmod +x on ALL files). Nose does the
136 # right thing and it tries to avoid executables,
148 # right thing and it tries to avoid executables,
137 # setuptools unfortunately forces our hand here. This
149 # setuptools unfortunately forces our hand here. This
138 # has been discussed on the distutils list and the
150 # has been discussed on the distutils list and the
139 # setuptools devs refuse to fix this problem!
151 # setuptools devs refuse to fix this problem!
140 '--exe',
152 '--exe',
141 ]
153 ]
142
154
143 # Detect if any tests were required by explicitly calling an IPython
155 # Detect if any tests were required by explicitly calling an IPython
144 # submodule or giving a specific path
156 # submodule or giving a specific path
145 has_tests = False
157 has_tests = False
146 for arg in sys.argv:
158 for arg in sys.argv:
147 if 'IPython' in arg or arg.endswith('.py') or \
159 if 'IPython' in arg or arg.endswith('.py') or \
148 (':' in arg and '.py' in arg):
160 (':' in arg and '.py' in arg):
149 has_tests = True
161 has_tests = True
150 break
162 break
151
163
152 # If nothing was specifically requested, test full IPython
164 # If nothing was specifically requested, test full IPython
153 if not has_tests:
165 if not has_tests:
154 argv.append('IPython')
166 argv.append('IPython')
155
167
156 # Construct list of plugins, omitting the existing doctest plugin, which
168 # Construct list of plugins, omitting the existing doctest plugin, which
157 # ours replaces (and extends).
169 # ours replaces (and extends).
170 EXCLUDE = make_exclude()
158 plugins = [IPythonDoctest(EXCLUDE)]
171 plugins = [IPythonDoctest(EXCLUDE)]
159 for p in nose.plugins.builtin.plugins:
172 for p in nose.plugins.builtin.plugins:
160 plug = p()
173 plug = p()
161 if plug.name == 'doctest':
174 if plug.name == 'doctest':
162 continue
175 continue
163
164 #print '*** adding plugin:',plug.name # dbg
165 plugins.append(plug)
176 plugins.append(plug)
166
177
167 TestProgram(argv=argv,plugins=plugins)
178 TestProgram(argv=argv,plugins=plugins)
168
179
169
180
170 class IPTester(object):
181 class IPTester(object):
171 """Call that calls iptest or trial in a subprocess.
182 """Call that calls iptest or trial in a subprocess.
172 """
183 """
173 def __init__(self,runner='iptest',params=None):
184 def __init__(self,runner='iptest',params=None):
174 """ """
185 """ """
175 if runner == 'iptest':
186 if runner == 'iptest':
176 self.runner = ['iptest','-v']
187 self.runner = ['iptest','-v']
177 else:
188 else:
178 self.runner = [find_cmd('trial')]
189 self.runner = [find_cmd('trial')]
179 if params is None:
190 if params is None:
180 params = []
191 params = []
181 if isinstance(params,str):
192 if isinstance(params,str):
182 params = [params]
193 params = [params]
183 self.params = params
194 self.params = params
184
195
185 # Assemble call
196 # Assemble call
186 self.call_args = self.runner+self.params
197 self.call_args = self.runner+self.params
187
198
188 def run(self):
199 def run(self):
189 """Run the stored commands"""
200 """Run the stored commands"""
190 return subprocess.call(self.call_args)
201 return subprocess.call(self.call_args)
191
202
192
203
193 def make_runners():
204 def make_runners():
194 """Define the modules and packages that need to be tested.
205 """Define the top-level packages that need to be tested.
195 """
206 """
196
197 # This omits additional top-level modules that should not be doctested.
198 # XXX: shell.py is also ommited because of a bug in the skip_doctest
199 # decorator. See ticket https://bugs.launchpad.net/bugs/366209
200 top_mod = \
201 ['backgroundjobs.py', 'coloransi.py', 'completer.py', 'configloader.py',
202 'crashhandler.py', 'debugger.py', 'deepreload.py', 'demo.py',
203 'DPyGetOpt.py', 'dtutils.py', 'excolors.py', 'fakemodule.py',
204 'generics.py', 'genutils.py', 'history.py', 'hooks.py', 'ipapi.py',
205 'iplib.py', 'ipmaker.py', 'ipstruct.py', 'Itpl.py',
206 'logger.py', 'macro.py', 'magic.py', 'oinspect.py',
207 'outputtrap.py', 'platutils.py', 'prefilter.py', 'prompts.py',
208 'PyColorize.py', 'release.py', 'rlineimpl.py', 'shadowns.py',
209 'shellglobals.py', 'strdispatch.py', 'twshell.py',
210 'ultratb.py', 'upgradedir.py', 'usage.py', 'wildcard.py',
211 # See note above for why this is skipped
212 # 'shell.py',
213 'winconsole.py']
214
215 if have_pexpect:
216 top_mod.append('irunner.py')
217
207
218 if sys.platform == 'win32':
208 nose_packages = ['config', 'core', 'extensions',
219 top_mod.append('platutils_win32.py')
209 'frontend', 'lib', 'quarantine',
220 elif os.name == 'posix':
210 'scripts', 'testing', 'utils']
221 top_mod.append('platutils_posix.py')
211 trial_packages = ['kernel']
222 else:
223 top_mod.append('platutils_dummy.py')
224
225 # These are tested by nose, so skip IPython.kernel
226 top_pack = ['config','extensions','frontend',
227 'testing','tests','tools','userconfig']
228
212
229 if have_wx:
213 if have_wx:
230 top_pack.append('gui')
214 nose_packages.append('gui')
231
215
232 modules = ['IPython.%s' % m[:-3] for m in top_mod ]
216 nose_packages = ['IPython.%s' % m for m in nose_packages ]
233 packages = ['IPython.%s' % m for m in top_pack ]
217 trial_packages = ['IPython.%s' % m for m in trial_packages ]
234
218
235 # Make runners
219 # Make runners
236 runners = dict(zip(top_pack, [IPTester(params=v) for v in packages]))
220 runners = dict()
237
221
238 # Test IPython.kernel using trial if twisted is installed
222 nose_runners = dict(zip(nose_packages, [IPTester(params=v) for v in nose_packages]))
239 if have_zi and have_twisted and have_foolscap:
223 if have_zi and have_twisted and have_foolscap:
240 runners['trial'] = IPTester('trial',['IPython'])
224 trial_runners = dict(zip(trial_packages, [IPTester('trial',params=v) for v in trial_packages]))
241
225 runners.update(nose_runners)
242 runners['modules'] = IPTester(params=modules)
226 runners.update(trial_runners)
243
227
244 return runners
228 return runners
245
229
246
230
247 def run_iptestall():
231 def run_iptestall():
248 """Run the entire IPython test suite by calling nose and trial.
232 """Run the entire IPython test suite by calling nose and trial.
249
233
250 This function constructs :class:`IPTester` instances for all IPython
234 This function constructs :class:`IPTester` instances for all IPython
251 modules and package and then runs each of them. This causes the modules
235 modules and package and then runs each of them. This causes the modules
252 and packages of IPython to be tested each in their own subprocess using
236 and packages of IPython to be tested each in their own subprocess using
253 nose or twisted.trial appropriately.
237 nose or twisted.trial appropriately.
254 """
238 """
239
255 runners = make_runners()
240 runners = make_runners()
241
256 # Run all test runners, tracking execution time
242 # Run all test runners, tracking execution time
257 failed = {}
243 failed = {}
258 t_start = time.time()
244 t_start = time.time()
259 for name,runner in runners.iteritems():
245 for name,runner in runners.iteritems():
260 print '*'*77
246 print '*'*77
261 print 'IPython test set:',name
247 print 'IPython test set:', name
262 res = runner.run()
248 res = runner.run()
263 if res:
249 if res:
264 failed[name] = res
250 failed[name] = res
265 t_end = time.time()
251 t_end = time.time()
266 t_tests = t_end - t_start
252 t_tests = t_end - t_start
267 nrunners = len(runners)
253 nrunners = len(runners)
268 nfail = len(failed)
254 nfail = len(failed)
269 # summarize results
255 # summarize results
270 print
256 print
271 print '*'*77
257 print '*'*77
272 print 'Ran %s test sets in %.3fs' % (nrunners, t_tests)
258 print 'Ran %s test sets in %.3fs' % (nrunners, t_tests)
273 print
259 print
274 if not failed:
260 if not failed:
275 print 'OK'
261 print 'OK'
276 else:
262 else:
277 # If anything went wrong, point out what command to rerun manually to
263 # If anything went wrong, point out what command to rerun manually to
278 # see the actual errors and individual summary
264 # see the actual errors and individual summary
279 print 'ERROR - %s out of %s test sets failed.' % (nfail, nrunners)
265 print 'ERROR - %s out of %s test sets failed.' % (nfail, nrunners)
280 for name in failed:
266 for name in failed:
281 failed_runner = runners[name]
267 failed_runner = runners[name]
282 print '-'*40
268 print '-'*40
283 print 'Runner failed:',name
269 print 'Runner failed:',name
284 print 'You may wish to rerun this one individually, with:'
270 print 'You may wish to rerun this one individually, with:'
285 print ' '.join(failed_runner.call_args)
271 print ' '.join(failed_runner.call_args)
286 print
272 print
287
273
288
274
289 def main():
275 def main():
290 if len(sys.argv) == 1:
276 if len(sys.argv) == 1:
291 run_iptestall()
277 run_iptestall()
292 else:
278 else:
293 if sys.argv[1] == 'all':
279 if sys.argv[1] == 'all':
294 run_iptestall()
280 run_iptestall()
295 else:
281 else:
296 run_iptest()
282 run_iptest()
297
283
298
284
299 if __name__ == '__main__':
285 if __name__ == '__main__':
300 main() No newline at end of file
286 main()
@@ -1,2261 +1,2261 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """General purpose utilities.
2 """General purpose utilities.
3
3
4 This is a grab-bag of stuff I find useful in most programs I write. Some of
4 This is a grab-bag of stuff I find useful in most programs I write. Some of
5 these things are also convenient when working at the command line.
5 these things are also convenient when working at the command line.
6 """
6 """
7
7
8 #*****************************************************************************
8 #*****************************************************************************
9 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
9 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #*****************************************************************************
13 #*****************************************************************************
14
14
15 #****************************************************************************
15 #****************************************************************************
16 # required modules from the Python standard library
16 # required modules from the Python standard library
17 import __main__
17 import __main__
18 import commands
18 import commands
19 try:
19 try:
20 import doctest
20 import doctest
21 except ImportError:
21 except ImportError:
22 pass
22 pass
23 import os
23 import os
24 import platform
24 import platform
25 import re
25 import re
26 import shlex
26 import shlex
27 import shutil
27 import shutil
28 import subprocess
28 import subprocess
29 import sys
29 import sys
30 import tempfile
30 import tempfile
31 import time
31 import time
32 import types
32 import types
33 import warnings
33 import warnings
34
34
35 # Curses and termios are Unix-only modules
35 # Curses and termios are Unix-only modules
36 try:
36 try:
37 import curses
37 import curses
38 # We need termios as well, so if its import happens to raise, we bail on
38 # We need termios as well, so if its import happens to raise, we bail on
39 # using curses altogether.
39 # using curses altogether.
40 import termios
40 import termios
41 except ImportError:
41 except ImportError:
42 USE_CURSES = False
42 USE_CURSES = False
43 else:
43 else:
44 # Curses on Solaris may not be complete, so we can't use it there
44 # Curses on Solaris may not be complete, so we can't use it there
45 USE_CURSES = hasattr(curses,'initscr')
45 USE_CURSES = hasattr(curses,'initscr')
46
46
47 # Other IPython utilities
47 # Other IPython utilities
48 import IPython
48 import IPython
49 from IPython.external.Itpl import Itpl,itpl,printpl
49 from IPython.external.Itpl import Itpl,itpl,printpl
50 from IPython.utils import platutils
50 from IPython.utils import platutils
51 from IPython.utils import DPyGetOpt
51 from IPython.utils import DPyGetOpt
52 from IPython.utils.generics import result_display
52 from IPython.utils.generics import result_display
53 from IPython.core import ipapi
53 from IPython.core import ipapi
54 from IPython.external.path import path
54 from IPython.external.path import path
55 if os.name == "nt":
55 if os.name == "nt":
56 from IPython.utils.winconsole import get_console_size
56 from IPython.utils.winconsole import get_console_size
57
57
58 try:
58 try:
59 set
59 set
60 except:
60 except:
61 from sets import Set as set
61 from sets import Set as set
62
62
63
63
64 #****************************************************************************
64 #****************************************************************************
65 # Exceptions
65 # Exceptions
66 class Error(Exception):
66 class Error(Exception):
67 """Base class for exceptions in this module."""
67 """Base class for exceptions in this module."""
68 pass
68 pass
69
69
70 #----------------------------------------------------------------------------
70 #----------------------------------------------------------------------------
71 class IOStream:
71 class IOStream:
72 def __init__(self,stream,fallback):
72 def __init__(self,stream,fallback):
73 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
73 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
74 stream = fallback
74 stream = fallback
75 self.stream = stream
75 self.stream = stream
76 self._swrite = stream.write
76 self._swrite = stream.write
77 self.flush = stream.flush
77 self.flush = stream.flush
78
78
79 def write(self,data):
79 def write(self,data):
80 try:
80 try:
81 self._swrite(data)
81 self._swrite(data)
82 except:
82 except:
83 try:
83 try:
84 # print handles some unicode issues which may trip a plain
84 # print handles some unicode issues which may trip a plain
85 # write() call. Attempt to emulate write() by using a
85 # write() call. Attempt to emulate write() by using a
86 # trailing comma
86 # trailing comma
87 print >> self.stream, data,
87 print >> self.stream, data,
88 except:
88 except:
89 # if we get here, something is seriously broken.
89 # if we get here, something is seriously broken.
90 print >> sys.stderr, \
90 print >> sys.stderr, \
91 'ERROR - failed to write data to stream:', self.stream
91 'ERROR - failed to write data to stream:', self.stream
92
92
93 def close(self):
93 def close(self):
94 pass
94 pass
95
95
96
96
97 class IOTerm:
97 class IOTerm:
98 """ Term holds the file or file-like objects for handling I/O operations.
98 """ Term holds the file or file-like objects for handling I/O operations.
99
99
100 These are normally just sys.stdin, sys.stdout and sys.stderr but for
100 These are normally just sys.stdin, sys.stdout and sys.stderr but for
101 Windows they can can replaced to allow editing the strings before they are
101 Windows they can can replaced to allow editing the strings before they are
102 displayed."""
102 displayed."""
103
103
104 # In the future, having IPython channel all its I/O operations through
104 # In the future, having IPython channel all its I/O operations through
105 # this class will make it easier to embed it into other environments which
105 # this class will make it easier to embed it into other environments which
106 # are not a normal terminal (such as a GUI-based shell)
106 # are not a normal terminal (such as a GUI-based shell)
107 def __init__(self,cin=None,cout=None,cerr=None):
107 def __init__(self,cin=None,cout=None,cerr=None):
108 self.cin = IOStream(cin,sys.stdin)
108 self.cin = IOStream(cin,sys.stdin)
109 self.cout = IOStream(cout,sys.stdout)
109 self.cout = IOStream(cout,sys.stdout)
110 self.cerr = IOStream(cerr,sys.stderr)
110 self.cerr = IOStream(cerr,sys.stderr)
111
111
112 # Global variable to be used for all I/O
112 # Global variable to be used for all I/O
113 Term = IOTerm()
113 Term = IOTerm()
114
114
115 import IPython.utils.rlineimpl as readline
115 import IPython.utils.rlineimpl as readline
116 # Remake Term to use the readline i/o facilities
116 # Remake Term to use the readline i/o facilities
117 if sys.platform == 'win32' and readline.have_readline:
117 if sys.platform == 'win32' and readline.have_readline:
118
118
119 Term = IOTerm(cout=readline._outputfile,cerr=readline._outputfile)
119 Term = IOTerm(cout=readline._outputfile,cerr=readline._outputfile)
120
120
121
121
122 #****************************************************************************
122 #****************************************************************************
123 # Generic warning/error printer, used by everything else
123 # Generic warning/error printer, used by everything else
124 def warn(msg,level=2,exit_val=1):
124 def warn(msg,level=2,exit_val=1):
125 """Standard warning printer. Gives formatting consistency.
125 """Standard warning printer. Gives formatting consistency.
126
126
127 Output is sent to Term.cerr (sys.stderr by default).
127 Output is sent to Term.cerr (sys.stderr by default).
128
128
129 Options:
129 Options:
130
130
131 -level(2): allows finer control:
131 -level(2): allows finer control:
132 0 -> Do nothing, dummy function.
132 0 -> Do nothing, dummy function.
133 1 -> Print message.
133 1 -> Print message.
134 2 -> Print 'WARNING:' + message. (Default level).
134 2 -> Print 'WARNING:' + message. (Default level).
135 3 -> Print 'ERROR:' + message.
135 3 -> Print 'ERROR:' + message.
136 4 -> Print 'FATAL ERROR:' + message and trigger a sys.exit(exit_val).
136 4 -> Print 'FATAL ERROR:' + message and trigger a sys.exit(exit_val).
137
137
138 -exit_val (1): exit value returned by sys.exit() for a level 4
138 -exit_val (1): exit value returned by sys.exit() for a level 4
139 warning. Ignored for all other levels."""
139 warning. Ignored for all other levels."""
140
140
141 if level>0:
141 if level>0:
142 header = ['','','WARNING: ','ERROR: ','FATAL ERROR: ']
142 header = ['','','WARNING: ','ERROR: ','FATAL ERROR: ']
143 print >> Term.cerr, '%s%s' % (header[level],msg)
143 print >> Term.cerr, '%s%s' % (header[level],msg)
144 if level == 4:
144 if level == 4:
145 print >> Term.cerr,'Exiting.\n'
145 print >> Term.cerr,'Exiting.\n'
146 sys.exit(exit_val)
146 sys.exit(exit_val)
147
147
148 def info(msg):
148 def info(msg):
149 """Equivalent to warn(msg,level=1)."""
149 """Equivalent to warn(msg,level=1)."""
150
150
151 warn(msg,level=1)
151 warn(msg,level=1)
152
152
153 def error(msg):
153 def error(msg):
154 """Equivalent to warn(msg,level=3)."""
154 """Equivalent to warn(msg,level=3)."""
155
155
156 warn(msg,level=3)
156 warn(msg,level=3)
157
157
158 def fatal(msg,exit_val=1):
158 def fatal(msg,exit_val=1):
159 """Equivalent to warn(msg,exit_val=exit_val,level=4)."""
159 """Equivalent to warn(msg,exit_val=exit_val,level=4)."""
160
160
161 warn(msg,exit_val=exit_val,level=4)
161 warn(msg,exit_val=exit_val,level=4)
162
162
163 #---------------------------------------------------------------------------
163 #---------------------------------------------------------------------------
164 # Debugging routines
164 # Debugging routines
165 #
165 #
166 def debugx(expr,pre_msg=''):
166 def debugx(expr,pre_msg=''):
167 """Print the value of an expression from the caller's frame.
167 """Print the value of an expression from the caller's frame.
168
168
169 Takes an expression, evaluates it in the caller's frame and prints both
169 Takes an expression, evaluates it in the caller's frame and prints both
170 the given expression and the resulting value (as well as a debug mark
170 the given expression and the resulting value (as well as a debug mark
171 indicating the name of the calling function. The input must be of a form
171 indicating the name of the calling function. The input must be of a form
172 suitable for eval().
172 suitable for eval().
173
173
174 An optional message can be passed, which will be prepended to the printed
174 An optional message can be passed, which will be prepended to the printed
175 expr->value pair."""
175 expr->value pair."""
176
176
177 cf = sys._getframe(1)
177 cf = sys._getframe(1)
178 print '[DBG:%s] %s%s -> %r' % (cf.f_code.co_name,pre_msg,expr,
178 print '[DBG:%s] %s%s -> %r' % (cf.f_code.co_name,pre_msg,expr,
179 eval(expr,cf.f_globals,cf.f_locals))
179 eval(expr,cf.f_globals,cf.f_locals))
180
180
181 # deactivate it by uncommenting the following line, which makes it a no-op
181 # deactivate it by uncommenting the following line, which makes it a no-op
182 #def debugx(expr,pre_msg=''): pass
182 #def debugx(expr,pre_msg=''): pass
183
183
184 #----------------------------------------------------------------------------
184 #----------------------------------------------------------------------------
185 StringTypes = types.StringTypes
185 StringTypes = types.StringTypes
186
186
187 # Basic timing functionality
187 # Basic timing functionality
188
188
189 # If possible (Unix), use the resource module instead of time.clock()
189 # If possible (Unix), use the resource module instead of time.clock()
190 try:
190 try:
191 import resource
191 import resource
192 def clocku():
192 def clocku():
193 """clocku() -> floating point number
193 """clocku() -> floating point number
194
194
195 Return the *USER* CPU time in seconds since the start of the process.
195 Return the *USER* CPU time in seconds since the start of the process.
196 This is done via a call to resource.getrusage, so it avoids the
196 This is done via a call to resource.getrusage, so it avoids the
197 wraparound problems in time.clock()."""
197 wraparound problems in time.clock()."""
198
198
199 return resource.getrusage(resource.RUSAGE_SELF)[0]
199 return resource.getrusage(resource.RUSAGE_SELF)[0]
200
200
201 def clocks():
201 def clocks():
202 """clocks() -> floating point number
202 """clocks() -> floating point number
203
203
204 Return the *SYSTEM* CPU time in seconds since the start of the process.
204 Return the *SYSTEM* CPU time in seconds since the start of the process.
205 This is done via a call to resource.getrusage, so it avoids the
205 This is done via a call to resource.getrusage, so it avoids the
206 wraparound problems in time.clock()."""
206 wraparound problems in time.clock()."""
207
207
208 return resource.getrusage(resource.RUSAGE_SELF)[1]
208 return resource.getrusage(resource.RUSAGE_SELF)[1]
209
209
210 def clock():
210 def clock():
211 """clock() -> floating point number
211 """clock() -> floating point number
212
212
213 Return the *TOTAL USER+SYSTEM* CPU time in seconds since the start of
213 Return the *TOTAL USER+SYSTEM* CPU time in seconds since the start of
214 the process. This is done via a call to resource.getrusage, so it
214 the process. This is done via a call to resource.getrusage, so it
215 avoids the wraparound problems in time.clock()."""
215 avoids the wraparound problems in time.clock()."""
216
216
217 u,s = resource.getrusage(resource.RUSAGE_SELF)[:2]
217 u,s = resource.getrusage(resource.RUSAGE_SELF)[:2]
218 return u+s
218 return u+s
219
219
220 def clock2():
220 def clock2():
221 """clock2() -> (t_user,t_system)
221 """clock2() -> (t_user,t_system)
222
222
223 Similar to clock(), but return a tuple of user/system times."""
223 Similar to clock(), but return a tuple of user/system times."""
224 return resource.getrusage(resource.RUSAGE_SELF)[:2]
224 return resource.getrusage(resource.RUSAGE_SELF)[:2]
225
225
226 except ImportError:
226 except ImportError:
227 # There is no distinction of user/system time under windows, so we just use
227 # There is no distinction of user/system time under windows, so we just use
228 # time.clock() for everything...
228 # time.clock() for everything...
229 clocku = clocks = clock = time.clock
229 clocku = clocks = clock = time.clock
230 def clock2():
230 def clock2():
231 """Under windows, system CPU time can't be measured.
231 """Under windows, system CPU time can't be measured.
232
232
233 This just returns clock() and zero."""
233 This just returns clock() and zero."""
234 return time.clock(),0.0
234 return time.clock(),0.0
235
235
236 def timings_out(reps,func,*args,**kw):
236 def timings_out(reps,func,*args,**kw):
237 """timings_out(reps,func,*args,**kw) -> (t_total,t_per_call,output)
237 """timings_out(reps,func,*args,**kw) -> (t_total,t_per_call,output)
238
238
239 Execute a function reps times, return a tuple with the elapsed total
239 Execute a function reps times, return a tuple with the elapsed total
240 CPU time in seconds, the time per call and the function's output.
240 CPU time in seconds, the time per call and the function's output.
241
241
242 Under Unix, the return value is the sum of user+system time consumed by
242 Under Unix, the return value is the sum of user+system time consumed by
243 the process, computed via the resource module. This prevents problems
243 the process, computed via the resource module. This prevents problems
244 related to the wraparound effect which the time.clock() function has.
244 related to the wraparound effect which the time.clock() function has.
245
245
246 Under Windows the return value is in wall clock seconds. See the
246 Under Windows the return value is in wall clock seconds. See the
247 documentation for the time module for more details."""
247 documentation for the time module for more details."""
248
248
249 reps = int(reps)
249 reps = int(reps)
250 assert reps >=1, 'reps must be >= 1'
250 assert reps >=1, 'reps must be >= 1'
251 if reps==1:
251 if reps==1:
252 start = clock()
252 start = clock()
253 out = func(*args,**kw)
253 out = func(*args,**kw)
254 tot_time = clock()-start
254 tot_time = clock()-start
255 else:
255 else:
256 rng = xrange(reps-1) # the last time is executed separately to store output
256 rng = xrange(reps-1) # the last time is executed separately to store output
257 start = clock()
257 start = clock()
258 for dummy in rng: func(*args,**kw)
258 for dummy in rng: func(*args,**kw)
259 out = func(*args,**kw) # one last time
259 out = func(*args,**kw) # one last time
260 tot_time = clock()-start
260 tot_time = clock()-start
261 av_time = tot_time / reps
261 av_time = tot_time / reps
262 return tot_time,av_time,out
262 return tot_time,av_time,out
263
263
264 def timings(reps,func,*args,**kw):
264 def timings(reps,func,*args,**kw):
265 """timings(reps,func,*args,**kw) -> (t_total,t_per_call)
265 """timings(reps,func,*args,**kw) -> (t_total,t_per_call)
266
266
267 Execute a function reps times, return a tuple with the elapsed total CPU
267 Execute a function reps times, return a tuple with the elapsed total CPU
268 time in seconds and the time per call. These are just the first two values
268 time in seconds and the time per call. These are just the first two values
269 in timings_out()."""
269 in timings_out()."""
270
270
271 return timings_out(reps,func,*args,**kw)[0:2]
271 return timings_out(reps,func,*args,**kw)[0:2]
272
272
273 def timing(func,*args,**kw):
273 def timing(func,*args,**kw):
274 """timing(func,*args,**kw) -> t_total
274 """timing(func,*args,**kw) -> t_total
275
275
276 Execute a function once, return the elapsed total CPU time in
276 Execute a function once, return the elapsed total CPU time in
277 seconds. This is just the first value in timings_out()."""
277 seconds. This is just the first value in timings_out()."""
278
278
279 return timings_out(1,func,*args,**kw)[0]
279 return timings_out(1,func,*args,**kw)[0]
280
280
281 #****************************************************************************
281 #****************************************************************************
282 # file and system
282 # file and system
283
283
284 def arg_split(s,posix=False):
284 def arg_split(s,posix=False):
285 """Split a command line's arguments in a shell-like manner.
285 """Split a command line's arguments in a shell-like manner.
286
286
287 This is a modified version of the standard library's shlex.split()
287 This is a modified version of the standard library's shlex.split()
288 function, but with a default of posix=False for splitting, so that quotes
288 function, but with a default of posix=False for splitting, so that quotes
289 in inputs are respected."""
289 in inputs are respected."""
290
290
291 # XXX - there may be unicode-related problems here!!! I'm not sure that
291 # XXX - there may be unicode-related problems here!!! I'm not sure that
292 # shlex is truly unicode-safe, so it might be necessary to do
292 # shlex is truly unicode-safe, so it might be necessary to do
293 #
293 #
294 # s = s.encode(sys.stdin.encoding)
294 # s = s.encode(sys.stdin.encoding)
295 #
295 #
296 # first, to ensure that shlex gets a normal string. Input from anyone who
296 # first, to ensure that shlex gets a normal string. Input from anyone who
297 # knows more about unicode and shlex than I would be good to have here...
297 # knows more about unicode and shlex than I would be good to have here...
298 lex = shlex.shlex(s, posix=posix)
298 lex = shlex.shlex(s, posix=posix)
299 lex.whitespace_split = True
299 lex.whitespace_split = True
300 return list(lex)
300 return list(lex)
301
301
302 def system(cmd,verbose=0,debug=0,header=''):
302 def system(cmd,verbose=0,debug=0,header=''):
303 """Execute a system command, return its exit status.
303 """Execute a system command, return its exit status.
304
304
305 Options:
305 Options:
306
306
307 - verbose (0): print the command to be executed.
307 - verbose (0): print the command to be executed.
308
308
309 - debug (0): only print, do not actually execute.
309 - debug (0): only print, do not actually execute.
310
310
311 - header (''): Header to print on screen prior to the executed command (it
311 - header (''): Header to print on screen prior to the executed command (it
312 is only prepended to the command, no newlines are added).
312 is only prepended to the command, no newlines are added).
313
313
314 Note: a stateful version of this function is available through the
314 Note: a stateful version of this function is available through the
315 SystemExec class."""
315 SystemExec class."""
316
316
317 stat = 0
317 stat = 0
318 if verbose or debug: print header+cmd
318 if verbose or debug: print header+cmd
319 sys.stdout.flush()
319 sys.stdout.flush()
320 if not debug: stat = os.system(cmd)
320 if not debug: stat = os.system(cmd)
321 return stat
321 return stat
322
322
323 def abbrev_cwd():
323 def abbrev_cwd():
324 """ Return abbreviated version of cwd, e.g. d:mydir """
324 """ Return abbreviated version of cwd, e.g. d:mydir """
325 cwd = os.getcwd().replace('\\','/')
325 cwd = os.getcwd().replace('\\','/')
326 drivepart = ''
326 drivepart = ''
327 tail = cwd
327 tail = cwd
328 if sys.platform == 'win32':
328 if sys.platform == 'win32':
329 if len(cwd) < 4:
329 if len(cwd) < 4:
330 return cwd
330 return cwd
331 drivepart,tail = os.path.splitdrive(cwd)
331 drivepart,tail = os.path.splitdrive(cwd)
332
332
333
333
334 parts = tail.split('/')
334 parts = tail.split('/')
335 if len(parts) > 2:
335 if len(parts) > 2:
336 tail = '/'.join(parts[-2:])
336 tail = '/'.join(parts[-2:])
337
337
338 return (drivepart + (
338 return (drivepart + (
339 cwd == '/' and '/' or tail))
339 cwd == '/' and '/' or tail))
340
340
341
341
342 # This function is used by ipython in a lot of places to make system calls.
342 # This function is used by ipython in a lot of places to make system calls.
343 # We need it to be slightly different under win32, due to the vagaries of
343 # We need it to be slightly different under win32, due to the vagaries of
344 # 'network shares'. A win32 override is below.
344 # 'network shares'. A win32 override is below.
345
345
346 def shell(cmd,verbose=0,debug=0,header=''):
346 def shell(cmd,verbose=0,debug=0,header=''):
347 """Execute a command in the system shell, always return None.
347 """Execute a command in the system shell, always return None.
348
348
349 Options:
349 Options:
350
350
351 - verbose (0): print the command to be executed.
351 - verbose (0): print the command to be executed.
352
352
353 - debug (0): only print, do not actually execute.
353 - debug (0): only print, do not actually execute.
354
354
355 - header (''): Header to print on screen prior to the executed command (it
355 - header (''): Header to print on screen prior to the executed command (it
356 is only prepended to the command, no newlines are added).
356 is only prepended to the command, no newlines are added).
357
357
358 Note: this is similar to genutils.system(), but it returns None so it can
358 Note: this is similar to genutils.system(), but it returns None so it can
359 be conveniently used in interactive loops without getting the return value
359 be conveniently used in interactive loops without getting the return value
360 (typically 0) printed many times."""
360 (typically 0) printed many times."""
361
361
362 stat = 0
362 stat = 0
363 if verbose or debug: print header+cmd
363 if verbose or debug: print header+cmd
364 # flush stdout so we don't mangle python's buffering
364 # flush stdout so we don't mangle python's buffering
365 sys.stdout.flush()
365 sys.stdout.flush()
366
366
367 if not debug:
367 if not debug:
368 platutils.set_term_title("IPy " + cmd)
368 platutils.set_term_title("IPy " + cmd)
369 os.system(cmd)
369 os.system(cmd)
370 platutils.set_term_title("IPy " + abbrev_cwd())
370 platutils.set_term_title("IPy " + abbrev_cwd())
371
371
372 # override shell() for win32 to deal with network shares
372 # override shell() for win32 to deal with network shares
373 if os.name in ('nt','dos'):
373 if os.name in ('nt','dos'):
374
374
375 shell_ori = shell
375 shell_ori = shell
376
376
377 def shell(cmd,verbose=0,debug=0,header=''):
377 def shell(cmd,verbose=0,debug=0,header=''):
378 if os.getcwd().startswith(r"\\"):
378 if os.getcwd().startswith(r"\\"):
379 path = os.getcwd()
379 path = os.getcwd()
380 # change to c drive (cannot be on UNC-share when issuing os.system,
380 # change to c drive (cannot be on UNC-share when issuing os.system,
381 # as cmd.exe cannot handle UNC addresses)
381 # as cmd.exe cannot handle UNC addresses)
382 os.chdir("c:")
382 os.chdir("c:")
383 # issue pushd to the UNC-share and then run the command
383 # issue pushd to the UNC-share and then run the command
384 try:
384 try:
385 shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header)
385 shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header)
386 finally:
386 finally:
387 os.chdir(path)
387 os.chdir(path)
388 else:
388 else:
389 shell_ori(cmd,verbose,debug,header)
389 shell_ori(cmd,verbose,debug,header)
390
390
391 shell.__doc__ = shell_ori.__doc__
391 shell.__doc__ = shell_ori.__doc__
392
392
393 def getoutput(cmd,verbose=0,debug=0,header='',split=0):
393 def getoutput(cmd,verbose=0,debug=0,header='',split=0):
394 """Dummy substitute for perl's backquotes.
394 """Dummy substitute for perl's backquotes.
395
395
396 Executes a command and returns the output.
396 Executes a command and returns the output.
397
397
398 Accepts the same arguments as system(), plus:
398 Accepts the same arguments as system(), plus:
399
399
400 - split(0): if true, the output is returned as a list split on newlines.
400 - split(0): if true, the output is returned as a list split on newlines.
401
401
402 Note: a stateful version of this function is available through the
402 Note: a stateful version of this function is available through the
403 SystemExec class.
403 SystemExec class.
404
404
405 This is pretty much deprecated and rarely used,
405 This is pretty much deprecated and rarely used,
406 genutils.getoutputerror may be what you need.
406 genutils.getoutputerror may be what you need.
407
407
408 """
408 """
409
409
410 if verbose or debug: print header+cmd
410 if verbose or debug: print header+cmd
411 if not debug:
411 if not debug:
412 output = os.popen(cmd).read()
412 output = os.popen(cmd).read()
413 # stipping last \n is here for backwards compat.
413 # stipping last \n is here for backwards compat.
414 if output.endswith('\n'):
414 if output.endswith('\n'):
415 output = output[:-1]
415 output = output[:-1]
416 if split:
416 if split:
417 return output.split('\n')
417 return output.split('\n')
418 else:
418 else:
419 return output
419 return output
420
420
421 def getoutputerror(cmd,verbose=0,debug=0,header='',split=0):
421 def getoutputerror(cmd,verbose=0,debug=0,header='',split=0):
422 """Return (standard output,standard error) of executing cmd in a shell.
422 """Return (standard output,standard error) of executing cmd in a shell.
423
423
424 Accepts the same arguments as system(), plus:
424 Accepts the same arguments as system(), plus:
425
425
426 - split(0): if true, each of stdout/err is returned as a list split on
426 - split(0): if true, each of stdout/err is returned as a list split on
427 newlines.
427 newlines.
428
428
429 Note: a stateful version of this function is available through the
429 Note: a stateful version of this function is available through the
430 SystemExec class."""
430 SystemExec class."""
431
431
432 if verbose or debug: print header+cmd
432 if verbose or debug: print header+cmd
433 if not cmd:
433 if not cmd:
434 if split:
434 if split:
435 return [],[]
435 return [],[]
436 else:
436 else:
437 return '',''
437 return '',''
438 if not debug:
438 if not debug:
439 pin,pout,perr = os.popen3(cmd)
439 pin,pout,perr = os.popen3(cmd)
440 tout = pout.read().rstrip()
440 tout = pout.read().rstrip()
441 terr = perr.read().rstrip()
441 terr = perr.read().rstrip()
442 pin.close()
442 pin.close()
443 pout.close()
443 pout.close()
444 perr.close()
444 perr.close()
445 if split:
445 if split:
446 return tout.split('\n'),terr.split('\n')
446 return tout.split('\n'),terr.split('\n')
447 else:
447 else:
448 return tout,terr
448 return tout,terr
449
449
450 # for compatibility with older naming conventions
450 # for compatibility with older naming conventions
451 xsys = system
451 xsys = system
452 bq = getoutput
452 bq = getoutput
453
453
454 class SystemExec:
454 class SystemExec:
455 """Access the system and getoutput functions through a stateful interface.
455 """Access the system and getoutput functions through a stateful interface.
456
456
457 Note: here we refer to the system and getoutput functions from this
457 Note: here we refer to the system and getoutput functions from this
458 library, not the ones from the standard python library.
458 library, not the ones from the standard python library.
459
459
460 This class offers the system and getoutput functions as methods, but the
460 This class offers the system and getoutput functions as methods, but the
461 verbose, debug and header parameters can be set for the instance (at
461 verbose, debug and header parameters can be set for the instance (at
462 creation time or later) so that they don't need to be specified on each
462 creation time or later) so that they don't need to be specified on each
463 call.
463 call.
464
464
465 For efficiency reasons, there's no way to override the parameters on a
465 For efficiency reasons, there's no way to override the parameters on a
466 per-call basis other than by setting instance attributes. If you need
466 per-call basis other than by setting instance attributes. If you need
467 local overrides, it's best to directly call system() or getoutput().
467 local overrides, it's best to directly call system() or getoutput().
468
468
469 The following names are provided as alternate options:
469 The following names are provided as alternate options:
470 - xsys: alias to system
470 - xsys: alias to system
471 - bq: alias to getoutput
471 - bq: alias to getoutput
472
472
473 An instance can then be created as:
473 An instance can then be created as:
474 >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ')
474 >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ')
475 """
475 """
476
476
477 def __init__(self,verbose=0,debug=0,header='',split=0):
477 def __init__(self,verbose=0,debug=0,header='',split=0):
478 """Specify the instance's values for verbose, debug and header."""
478 """Specify the instance's values for verbose, debug and header."""
479 setattr_list(self,'verbose debug header split')
479 setattr_list(self,'verbose debug header split')
480
480
481 def system(self,cmd):
481 def system(self,cmd):
482 """Stateful interface to system(), with the same keyword parameters."""
482 """Stateful interface to system(), with the same keyword parameters."""
483
483
484 system(cmd,self.verbose,self.debug,self.header)
484 system(cmd,self.verbose,self.debug,self.header)
485
485
486 def shell(self,cmd):
486 def shell(self,cmd):
487 """Stateful interface to shell(), with the same keyword parameters."""
487 """Stateful interface to shell(), with the same keyword parameters."""
488
488
489 shell(cmd,self.verbose,self.debug,self.header)
489 shell(cmd,self.verbose,self.debug,self.header)
490
490
491 xsys = system # alias
491 xsys = system # alias
492
492
493 def getoutput(self,cmd):
493 def getoutput(self,cmd):
494 """Stateful interface to getoutput()."""
494 """Stateful interface to getoutput()."""
495
495
496 return getoutput(cmd,self.verbose,self.debug,self.header,self.split)
496 return getoutput(cmd,self.verbose,self.debug,self.header,self.split)
497
497
498 def getoutputerror(self,cmd):
498 def getoutputerror(self,cmd):
499 """Stateful interface to getoutputerror()."""
499 """Stateful interface to getoutputerror()."""
500
500
501 return getoutputerror(cmd,self.verbose,self.debug,self.header,self.split)
501 return getoutputerror(cmd,self.verbose,self.debug,self.header,self.split)
502
502
503 bq = getoutput # alias
503 bq = getoutput # alias
504
504
505 #-----------------------------------------------------------------------------
505 #-----------------------------------------------------------------------------
506 def mutex_opts(dict,ex_op):
506 def mutex_opts(dict,ex_op):
507 """Check for presence of mutually exclusive keys in a dict.
507 """Check for presence of mutually exclusive keys in a dict.
508
508
509 Call: mutex_opts(dict,[[op1a,op1b],[op2a,op2b]...]"""
509 Call: mutex_opts(dict,[[op1a,op1b],[op2a,op2b]...]"""
510 for op1,op2 in ex_op:
510 for op1,op2 in ex_op:
511 if op1 in dict and op2 in dict:
511 if op1 in dict and op2 in dict:
512 raise ValueError,'\n*** ERROR in Arguments *** '\
512 raise ValueError,'\n*** ERROR in Arguments *** '\
513 'Options '+op1+' and '+op2+' are mutually exclusive.'
513 'Options '+op1+' and '+op2+' are mutually exclusive.'
514
514
515 #-----------------------------------------------------------------------------
515 #-----------------------------------------------------------------------------
516 def get_py_filename(name):
516 def get_py_filename(name):
517 """Return a valid python filename in the current directory.
517 """Return a valid python filename in the current directory.
518
518
519 If the given name is not a file, it adds '.py' and searches again.
519 If the given name is not a file, it adds '.py' and searches again.
520 Raises IOError with an informative message if the file isn't found."""
520 Raises IOError with an informative message if the file isn't found."""
521
521
522 name = os.path.expanduser(name)
522 name = os.path.expanduser(name)
523 if not os.path.isfile(name) and not name.endswith('.py'):
523 if not os.path.isfile(name) and not name.endswith('.py'):
524 name += '.py'
524 name += '.py'
525 if os.path.isfile(name):
525 if os.path.isfile(name):
526 return name
526 return name
527 else:
527 else:
528 raise IOError,'File `%s` not found.' % name
528 raise IOError,'File `%s` not found.' % name
529
529
530 #-----------------------------------------------------------------------------
530 #-----------------------------------------------------------------------------
531 def filefind(fname,alt_dirs = None):
531 def filefind(fname,alt_dirs = None):
532 """Return the given filename either in the current directory, if it
532 """Return the given filename either in the current directory, if it
533 exists, or in a specified list of directories.
533 exists, or in a specified list of directories.
534
534
535 ~ expansion is done on all file and directory names.
535 ~ expansion is done on all file and directory names.
536
536
537 Upon an unsuccessful search, raise an IOError exception."""
537 Upon an unsuccessful search, raise an IOError exception."""
538
538
539 if alt_dirs is None:
539 if alt_dirs is None:
540 try:
540 try:
541 alt_dirs = get_home_dir()
541 alt_dirs = get_home_dir()
542 except HomeDirError:
542 except HomeDirError:
543 alt_dirs = os.getcwd()
543 alt_dirs = os.getcwd()
544 search = [fname] + list_strings(alt_dirs)
544 search = [fname] + list_strings(alt_dirs)
545 search = map(os.path.expanduser,search)
545 search = map(os.path.expanduser,search)
546 #print 'search list for',fname,'list:',search # dbg
546 #print 'search list for',fname,'list:',search # dbg
547 fname = search[0]
547 fname = search[0]
548 if os.path.isfile(fname):
548 if os.path.isfile(fname):
549 return fname
549 return fname
550 for direc in search[1:]:
550 for direc in search[1:]:
551 testname = os.path.join(direc,fname)
551 testname = os.path.join(direc,fname)
552 #print 'testname',testname # dbg
552 #print 'testname',testname # dbg
553 if os.path.isfile(testname):
553 if os.path.isfile(testname):
554 return testname
554 return testname
555 raise IOError,'File' + `fname` + \
555 raise IOError,'File' + `fname` + \
556 ' not found in current or supplied directories:' + `alt_dirs`
556 ' not found in current or supplied directories:' + `alt_dirs`
557
557
558 #----------------------------------------------------------------------------
558 #----------------------------------------------------------------------------
559 def file_read(filename):
559 def file_read(filename):
560 """Read a file and close it. Returns the file source."""
560 """Read a file and close it. Returns the file source."""
561 fobj = open(filename,'r');
561 fobj = open(filename,'r');
562 source = fobj.read();
562 source = fobj.read();
563 fobj.close()
563 fobj.close()
564 return source
564 return source
565
565
566 def file_readlines(filename):
566 def file_readlines(filename):
567 """Read a file and close it. Returns the file source using readlines()."""
567 """Read a file and close it. Returns the file source using readlines()."""
568 fobj = open(filename,'r');
568 fobj = open(filename,'r');
569 lines = fobj.readlines();
569 lines = fobj.readlines();
570 fobj.close()
570 fobj.close()
571 return lines
571 return lines
572
572
573 #----------------------------------------------------------------------------
573 #----------------------------------------------------------------------------
574 def target_outdated(target,deps):
574 def target_outdated(target,deps):
575 """Determine whether a target is out of date.
575 """Determine whether a target is out of date.
576
576
577 target_outdated(target,deps) -> 1/0
577 target_outdated(target,deps) -> 1/0
578
578
579 deps: list of filenames which MUST exist.
579 deps: list of filenames which MUST exist.
580 target: single filename which may or may not exist.
580 target: single filename which may or may not exist.
581
581
582 If target doesn't exist or is older than any file listed in deps, return
582 If target doesn't exist or is older than any file listed in deps, return
583 true, otherwise return false.
583 true, otherwise return false.
584 """
584 """
585 try:
585 try:
586 target_time = os.path.getmtime(target)
586 target_time = os.path.getmtime(target)
587 except os.error:
587 except os.error:
588 return 1
588 return 1
589 for dep in deps:
589 for dep in deps:
590 dep_time = os.path.getmtime(dep)
590 dep_time = os.path.getmtime(dep)
591 if dep_time > target_time:
591 if dep_time > target_time:
592 #print "For target",target,"Dep failed:",dep # dbg
592 #print "For target",target,"Dep failed:",dep # dbg
593 #print "times (dep,tar):",dep_time,target_time # dbg
593 #print "times (dep,tar):",dep_time,target_time # dbg
594 return 1
594 return 1
595 return 0
595 return 0
596
596
597 #-----------------------------------------------------------------------------
597 #-----------------------------------------------------------------------------
598 def target_update(target,deps,cmd):
598 def target_update(target,deps,cmd):
599 """Update a target with a given command given a list of dependencies.
599 """Update a target with a given command given a list of dependencies.
600
600
601 target_update(target,deps,cmd) -> runs cmd if target is outdated.
601 target_update(target,deps,cmd) -> runs cmd if target is outdated.
602
602
603 This is just a wrapper around target_outdated() which calls the given
603 This is just a wrapper around target_outdated() which calls the given
604 command if target is outdated."""
604 command if target is outdated."""
605
605
606 if target_outdated(target,deps):
606 if target_outdated(target,deps):
607 xsys(cmd)
607 xsys(cmd)
608
608
609 #----------------------------------------------------------------------------
609 #----------------------------------------------------------------------------
610 def unquote_ends(istr):
610 def unquote_ends(istr):
611 """Remove a single pair of quotes from the endpoints of a string."""
611 """Remove a single pair of quotes from the endpoints of a string."""
612
612
613 if not istr:
613 if not istr:
614 return istr
614 return istr
615 if (istr[0]=="'" and istr[-1]=="'") or \
615 if (istr[0]=="'" and istr[-1]=="'") or \
616 (istr[0]=='"' and istr[-1]=='"'):
616 (istr[0]=='"' and istr[-1]=='"'):
617 return istr[1:-1]
617 return istr[1:-1]
618 else:
618 else:
619 return istr
619 return istr
620
620
621 #----------------------------------------------------------------------------
621 #----------------------------------------------------------------------------
622 def process_cmdline(argv,names=[],defaults={},usage=''):
622 def process_cmdline(argv,names=[],defaults={},usage=''):
623 """ Process command-line options and arguments.
623 """ Process command-line options and arguments.
624
624
625 Arguments:
625 Arguments:
626
626
627 - argv: list of arguments, typically sys.argv.
627 - argv: list of arguments, typically sys.argv.
628
628
629 - names: list of option names. See DPyGetOpt docs for details on options
629 - names: list of option names. See DPyGetOpt docs for details on options
630 syntax.
630 syntax.
631
631
632 - defaults: dict of default values.
632 - defaults: dict of default values.
633
633
634 - usage: optional usage notice to print if a wrong argument is passed.
634 - usage: optional usage notice to print if a wrong argument is passed.
635
635
636 Return a dict of options and a list of free arguments."""
636 Return a dict of options and a list of free arguments."""
637
637
638 getopt = DPyGetOpt.DPyGetOpt()
638 getopt = DPyGetOpt.DPyGetOpt()
639 getopt.setIgnoreCase(0)
639 getopt.setIgnoreCase(0)
640 getopt.parseConfiguration(names)
640 getopt.parseConfiguration(names)
641
641
642 try:
642 try:
643 getopt.processArguments(argv)
643 getopt.processArguments(argv)
644 except DPyGetOpt.ArgumentError, exc:
644 except DPyGetOpt.ArgumentError, exc:
645 print usage
645 print usage
646 warn('"%s"' % exc,level=4)
646 warn('"%s"' % exc,level=4)
647
647
648 defaults.update(getopt.optionValues)
648 defaults.update(getopt.optionValues)
649 args = getopt.freeValues
649 args = getopt.freeValues
650
650
651 return defaults,args
651 return defaults,args
652
652
653 #----------------------------------------------------------------------------
653 #----------------------------------------------------------------------------
654 def optstr2types(ostr):
654 def optstr2types(ostr):
655 """Convert a string of option names to a dict of type mappings.
655 """Convert a string of option names to a dict of type mappings.
656
656
657 optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'}
657 optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'}
658
658
659 This is used to get the types of all the options in a string formatted
659 This is used to get the types of all the options in a string formatted
660 with the conventions of DPyGetOpt. The 'type' None is used for options
660 with the conventions of DPyGetOpt. The 'type' None is used for options
661 which are strings (they need no further conversion). This function's main
661 which are strings (they need no further conversion). This function's main
662 use is to get a typemap for use with read_dict().
662 use is to get a typemap for use with read_dict().
663 """
663 """
664
664
665 typeconv = {None:'',int:'',float:''}
665 typeconv = {None:'',int:'',float:''}
666 typemap = {'s':None,'i':int,'f':float}
666 typemap = {'s':None,'i':int,'f':float}
667 opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)')
667 opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)')
668
668
669 for w in ostr.split():
669 for w in ostr.split():
670 oname,alias,otype = opt_re.match(w).groups()
670 oname,alias,otype = opt_re.match(w).groups()
671 if otype == '' or alias == '!': # simple switches are integers too
671 if otype == '' or alias == '!': # simple switches are integers too
672 otype = 'i'
672 otype = 'i'
673 typeconv[typemap[otype]] += oname + ' '
673 typeconv[typemap[otype]] += oname + ' '
674 return typeconv
674 return typeconv
675
675
676 #----------------------------------------------------------------------------
676 #----------------------------------------------------------------------------
677 def read_dict(filename,type_conv=None,**opt):
677 def read_dict(filename,type_conv=None,**opt):
678 r"""Read a dictionary of key=value pairs from an input file, optionally
678 r"""Read a dictionary of key=value pairs from an input file, optionally
679 performing conversions on the resulting values.
679 performing conversions on the resulting values.
680
680
681 read_dict(filename,type_conv,**opt) -> dict
681 read_dict(filename,type_conv,**opt) -> dict
682
682
683 Only one value per line is accepted, the format should be
683 Only one value per line is accepted, the format should be
684 # optional comments are ignored
684 # optional comments are ignored
685 key value\n
685 key value\n
686
686
687 Args:
687 Args:
688
688
689 - type_conv: A dictionary specifying which keys need to be converted to
689 - type_conv: A dictionary specifying which keys need to be converted to
690 which types. By default all keys are read as strings. This dictionary
690 which types. By default all keys are read as strings. This dictionary
691 should have as its keys valid conversion functions for strings
691 should have as its keys valid conversion functions for strings
692 (int,long,float,complex, or your own). The value for each key
692 (int,long,float,complex, or your own). The value for each key
693 (converter) should be a whitespace separated string containing the names
693 (converter) should be a whitespace separated string containing the names
694 of all the entries in the file to be converted using that function. For
694 of all the entries in the file to be converted using that function. For
695 keys to be left alone, use None as the conversion function (only needed
695 keys to be left alone, use None as the conversion function (only needed
696 with purge=1, see below).
696 with purge=1, see below).
697
697
698 - opt: dictionary with extra options as below (default in parens)
698 - opt: dictionary with extra options as below (default in parens)
699
699
700 purge(0): if set to 1, all keys *not* listed in type_conv are purged out
700 purge(0): if set to 1, all keys *not* listed in type_conv are purged out
701 of the dictionary to be returned. If purge is going to be used, the
701 of the dictionary to be returned. If purge is going to be used, the
702 set of keys to be left as strings also has to be explicitly specified
702 set of keys to be left as strings also has to be explicitly specified
703 using the (non-existent) conversion function None.
703 using the (non-existent) conversion function None.
704
704
705 fs(None): field separator. This is the key/value separator to be used
705 fs(None): field separator. This is the key/value separator to be used
706 when parsing the file. The None default means any whitespace [behavior
706 when parsing the file. The None default means any whitespace [behavior
707 of string.split()].
707 of string.split()].
708
708
709 strip(0): if 1, strip string values of leading/trailinig whitespace.
709 strip(0): if 1, strip string values of leading/trailinig whitespace.
710
710
711 warn(1): warning level if requested keys are not found in file.
711 warn(1): warning level if requested keys are not found in file.
712 - 0: silently ignore.
712 - 0: silently ignore.
713 - 1: inform but proceed.
713 - 1: inform but proceed.
714 - 2: raise KeyError exception.
714 - 2: raise KeyError exception.
715
715
716 no_empty(0): if 1, remove keys with whitespace strings as a value.
716 no_empty(0): if 1, remove keys with whitespace strings as a value.
717
717
718 unique([]): list of keys (or space separated string) which can't be
718 unique([]): list of keys (or space separated string) which can't be
719 repeated. If one such key is found in the file, each new instance
719 repeated. If one such key is found in the file, each new instance
720 overwrites the previous one. For keys not listed here, the behavior is
720 overwrites the previous one. For keys not listed here, the behavior is
721 to make a list of all appearances.
721 to make a list of all appearances.
722
722
723 Example:
723 Example:
724
724
725 If the input file test.ini contains (we put it in a string to keep the test
725 If the input file test.ini contains (we put it in a string to keep the test
726 self-contained):
726 self-contained):
727
727
728 >>> test_ini = '''\
728 >>> test_ini = '''\
729 ... i 3
729 ... i 3
730 ... x 4.5
730 ... x 4.5
731 ... y 5.5
731 ... y 5.5
732 ... s hi ho'''
732 ... s hi ho'''
733
733
734 Then we can use it as follows:
734 Then we can use it as follows:
735 >>> type_conv={int:'i',float:'x',None:'s'}
735 >>> type_conv={int:'i',float:'x',None:'s'}
736
736
737 >>> d = read_dict(test_ini)
737 >>> d = read_dict(test_ini)
738
738
739 >>> sorted(d.items())
739 >>> sorted(d.items())
740 [('i', '3'), ('s', 'hi ho'), ('x', '4.5'), ('y', '5.5')]
740 [('i', '3'), ('s', 'hi ho'), ('x', '4.5'), ('y', '5.5')]
741
741
742 >>> d = read_dict(test_ini,type_conv)
742 >>> d = read_dict(test_ini,type_conv)
743
743
744 >>> sorted(d.items())
744 >>> sorted(d.items())
745 [('i', 3), ('s', 'hi ho'), ('x', 4.5), ('y', '5.5')]
745 [('i', 3), ('s', 'hi ho'), ('x', 4.5), ('y', '5.5')]
746
746
747 >>> d = read_dict(test_ini,type_conv,purge=True)
747 >>> d = read_dict(test_ini,type_conv,purge=True)
748
748
749 >>> sorted(d.items())
749 >>> sorted(d.items())
750 [('i', 3), ('s', 'hi ho'), ('x', 4.5)]
750 [('i', 3), ('s', 'hi ho'), ('x', 4.5)]
751 """
751 """
752
752
753 # starting config
753 # starting config
754 opt.setdefault('purge',0)
754 opt.setdefault('purge',0)
755 opt.setdefault('fs',None) # field sep defaults to any whitespace
755 opt.setdefault('fs',None) # field sep defaults to any whitespace
756 opt.setdefault('strip',0)
756 opt.setdefault('strip',0)
757 opt.setdefault('warn',1)
757 opt.setdefault('warn',1)
758 opt.setdefault('no_empty',0)
758 opt.setdefault('no_empty',0)
759 opt.setdefault('unique','')
759 opt.setdefault('unique','')
760 if type(opt['unique']) in StringTypes:
760 if type(opt['unique']) in StringTypes:
761 unique_keys = qw(opt['unique'])
761 unique_keys = qw(opt['unique'])
762 elif type(opt['unique']) in (types.TupleType,types.ListType):
762 elif type(opt['unique']) in (types.TupleType,types.ListType):
763 unique_keys = opt['unique']
763 unique_keys = opt['unique']
764 else:
764 else:
765 raise ValueError, 'Unique keys must be given as a string, List or Tuple'
765 raise ValueError, 'Unique keys must be given as a string, List or Tuple'
766
766
767 dict = {}
767 dict = {}
768
768
769 # first read in table of values as strings
769 # first read in table of values as strings
770 if '\n' in filename:
770 if '\n' in filename:
771 lines = filename.splitlines()
771 lines = filename.splitlines()
772 file = None
772 file = None
773 else:
773 else:
774 file = open(filename,'r')
774 file = open(filename,'r')
775 lines = file.readlines()
775 lines = file.readlines()
776 for line in lines:
776 for line in lines:
777 line = line.strip()
777 line = line.strip()
778 if len(line) and line[0]=='#': continue
778 if len(line) and line[0]=='#': continue
779 if len(line)>0:
779 if len(line)>0:
780 lsplit = line.split(opt['fs'],1)
780 lsplit = line.split(opt['fs'],1)
781 try:
781 try:
782 key,val = lsplit
782 key,val = lsplit
783 except ValueError:
783 except ValueError:
784 key,val = lsplit[0],''
784 key,val = lsplit[0],''
785 key = key.strip()
785 key = key.strip()
786 if opt['strip']: val = val.strip()
786 if opt['strip']: val = val.strip()
787 if val == "''" or val == '""': val = ''
787 if val == "''" or val == '""': val = ''
788 if opt['no_empty'] and (val=='' or val.isspace()):
788 if opt['no_empty'] and (val=='' or val.isspace()):
789 continue
789 continue
790 # if a key is found more than once in the file, build a list
790 # if a key is found more than once in the file, build a list
791 # unless it's in the 'unique' list. In that case, last found in file
791 # unless it's in the 'unique' list. In that case, last found in file
792 # takes precedence. User beware.
792 # takes precedence. User beware.
793 try:
793 try:
794 if dict[key] and key in unique_keys:
794 if dict[key] and key in unique_keys:
795 dict[key] = val
795 dict[key] = val
796 elif type(dict[key]) is types.ListType:
796 elif type(dict[key]) is types.ListType:
797 dict[key].append(val)
797 dict[key].append(val)
798 else:
798 else:
799 dict[key] = [dict[key],val]
799 dict[key] = [dict[key],val]
800 except KeyError:
800 except KeyError:
801 dict[key] = val
801 dict[key] = val
802 # purge if requested
802 # purge if requested
803 if opt['purge']:
803 if opt['purge']:
804 accepted_keys = qwflat(type_conv.values())
804 accepted_keys = qwflat(type_conv.values())
805 for key in dict.keys():
805 for key in dict.keys():
806 if key in accepted_keys: continue
806 if key in accepted_keys: continue
807 del(dict[key])
807 del(dict[key])
808 # now convert if requested
808 # now convert if requested
809 if type_conv==None: return dict
809 if type_conv==None: return dict
810 conversions = type_conv.keys()
810 conversions = type_conv.keys()
811 try: conversions.remove(None)
811 try: conversions.remove(None)
812 except: pass
812 except: pass
813 for convert in conversions:
813 for convert in conversions:
814 for val in qw(type_conv[convert]):
814 for val in qw(type_conv[convert]):
815 try:
815 try:
816 dict[val] = convert(dict[val])
816 dict[val] = convert(dict[val])
817 except KeyError,e:
817 except KeyError,e:
818 if opt['warn'] == 0:
818 if opt['warn'] == 0:
819 pass
819 pass
820 elif opt['warn'] == 1:
820 elif opt['warn'] == 1:
821 print >>sys.stderr, 'Warning: key',val,\
821 print >>sys.stderr, 'Warning: key',val,\
822 'not found in file',filename
822 'not found in file',filename
823 elif opt['warn'] == 2:
823 elif opt['warn'] == 2:
824 raise KeyError,e
824 raise KeyError,e
825 else:
825 else:
826 raise ValueError,'Warning level must be 0,1 or 2'
826 raise ValueError,'Warning level must be 0,1 or 2'
827
827
828 return dict
828 return dict
829
829
830 #----------------------------------------------------------------------------
830 #----------------------------------------------------------------------------
831 def flag_calls(func):
831 def flag_calls(func):
832 """Wrap a function to detect and flag when it gets called.
832 """Wrap a function to detect and flag when it gets called.
833
833
834 This is a decorator which takes a function and wraps it in a function with
834 This is a decorator which takes a function and wraps it in a function with
835 a 'called' attribute. wrapper.called is initialized to False.
835 a 'called' attribute. wrapper.called is initialized to False.
836
836
837 The wrapper.called attribute is set to False right before each call to the
837 The wrapper.called attribute is set to False right before each call to the
838 wrapped function, so if the call fails it remains False. After the call
838 wrapped function, so if the call fails it remains False. After the call
839 completes, wrapper.called is set to True and the output is returned.
839 completes, wrapper.called is set to True and the output is returned.
840
840
841 Testing for truth in wrapper.called allows you to determine if a call to
841 Testing for truth in wrapper.called allows you to determine if a call to
842 func() was attempted and succeeded."""
842 func() was attempted and succeeded."""
843
843
844 def wrapper(*args,**kw):
844 def wrapper(*args,**kw):
845 wrapper.called = False
845 wrapper.called = False
846 out = func(*args,**kw)
846 out = func(*args,**kw)
847 wrapper.called = True
847 wrapper.called = True
848 return out
848 return out
849
849
850 wrapper.called = False
850 wrapper.called = False
851 wrapper.__doc__ = func.__doc__
851 wrapper.__doc__ = func.__doc__
852 return wrapper
852 return wrapper
853
853
854 #----------------------------------------------------------------------------
854 #----------------------------------------------------------------------------
855 def dhook_wrap(func,*a,**k):
855 def dhook_wrap(func,*a,**k):
856 """Wrap a function call in a sys.displayhook controller.
856 """Wrap a function call in a sys.displayhook controller.
857
857
858 Returns a wrapper around func which calls func, with all its arguments and
858 Returns a wrapper around func which calls func, with all its arguments and
859 keywords unmodified, using the default sys.displayhook. Since IPython
859 keywords unmodified, using the default sys.displayhook. Since IPython
860 modifies sys.displayhook, it breaks the behavior of certain systems that
860 modifies sys.displayhook, it breaks the behavior of certain systems that
861 rely on the default behavior, notably doctest.
861 rely on the default behavior, notably doctest.
862 """
862 """
863
863
864 def f(*a,**k):
864 def f(*a,**k):
865
865
866 dhook_s = sys.displayhook
866 dhook_s = sys.displayhook
867 sys.displayhook = sys.__displayhook__
867 sys.displayhook = sys.__displayhook__
868 try:
868 try:
869 out = func(*a,**k)
869 out = func(*a,**k)
870 finally:
870 finally:
871 sys.displayhook = dhook_s
871 sys.displayhook = dhook_s
872
872
873 return out
873 return out
874
874
875 f.__doc__ = func.__doc__
875 f.__doc__ = func.__doc__
876 return f
876 return f
877
877
878 #----------------------------------------------------------------------------
878 #----------------------------------------------------------------------------
879 def doctest_reload():
879 def doctest_reload():
880 """Properly reload doctest to reuse it interactively.
880 """Properly reload doctest to reuse it interactively.
881
881
882 This routine:
882 This routine:
883
883
884 - reloads doctest
884 - reloads doctest
885
885
886 - resets its global 'master' attribute to None, so that multiple uses of
886 - resets its global 'master' attribute to None, so that multiple uses of
887 the module interactively don't produce cumulative reports.
887 the module interactively don't produce cumulative reports.
888
888
889 - Monkeypatches its core test runner method to protect it from IPython's
889 - Monkeypatches its core test runner method to protect it from IPython's
890 modified displayhook. Doctest expects the default displayhook behavior
890 modified displayhook. Doctest expects the default displayhook behavior
891 deep down, so our modification breaks it completely. For this reason, a
891 deep down, so our modification breaks it completely. For this reason, a
892 hard monkeypatch seems like a reasonable solution rather than asking
892 hard monkeypatch seems like a reasonable solution rather than asking
893 users to manually use a different doctest runner when under IPython."""
893 users to manually use a different doctest runner when under IPython."""
894
894
895 import doctest
895 import doctest
896 reload(doctest)
896 reload(doctest)
897 doctest.master=None
897 doctest.master=None
898
898
899 try:
899 try:
900 doctest.DocTestRunner
900 doctest.DocTestRunner
901 except AttributeError:
901 except AttributeError:
902 # This is only for python 2.3 compatibility, remove once we move to
902 # This is only for python 2.3 compatibility, remove once we move to
903 # 2.4 only.
903 # 2.4 only.
904 pass
904 pass
905 else:
905 else:
906 doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run)
906 doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run)
907
907
908 #----------------------------------------------------------------------------
908 #----------------------------------------------------------------------------
909 class HomeDirError(Error):
909 class HomeDirError(Error):
910 pass
910 pass
911
911
912 def get_home_dir():
912 def get_home_dir():
913 """Return the closest possible equivalent to a 'home' directory.
913 """Return the closest possible equivalent to a 'home' directory.
914
914
915 We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH.
915 We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH.
916
916
917 Currently only Posix and NT are implemented, a HomeDirError exception is
917 Currently only Posix and NT are implemented, a HomeDirError exception is
918 raised for all other OSes. """
918 raised for all other OSes. """
919
919
920 isdir = os.path.isdir
920 isdir = os.path.isdir
921 env = os.environ
921 env = os.environ
922
922
923 # first, check py2exe distribution root directory for _ipython.
923 # first, check py2exe distribution root directory for _ipython.
924 # This overrides all. Normally does not exist.
924 # This overrides all. Normally does not exist.
925
925
926 if hasattr(sys, "frozen"): #Is frozen by py2exe
926 if hasattr(sys, "frozen"): #Is frozen by py2exe
927 if '\\library.zip\\' in IPython.__file__.lower():#libraries compressed to zip-file
927 if '\\library.zip\\' in IPython.__file__.lower():#libraries compressed to zip-file
928 root, rest = IPython.__file__.lower().split('library.zip')
928 root, rest = IPython.__file__.lower().split('library.zip')
929 else:
929 else:
930 root=os.path.join(os.path.split(IPython.__file__)[0],"../../")
930 root=os.path.join(os.path.split(IPython.__file__)[0],"../../")
931 root=os.path.abspath(root).rstrip('\\')
931 root=os.path.abspath(root).rstrip('\\')
932 if isdir(os.path.join(root, '_ipython')):
932 if isdir(os.path.join(root, '_ipython')):
933 os.environ["IPYKITROOT"] = root
933 os.environ["IPYKITROOT"] = root
934 return root
934 return root
935 try:
935 try:
936 homedir = env['HOME']
936 homedir = env['HOME']
937 if not isdir(homedir):
937 if not isdir(homedir):
938 # in case a user stuck some string which does NOT resolve to a
938 # in case a user stuck some string which does NOT resolve to a
939 # valid path, it's as good as if we hadn't foud it
939 # valid path, it's as good as if we hadn't foud it
940 raise KeyError
940 raise KeyError
941 return homedir
941 return homedir
942 except KeyError:
942 except KeyError:
943 if os.name == 'posix':
943 if os.name == 'posix':
944 raise HomeDirError,'undefined $HOME, IPython can not proceed.'
944 raise HomeDirError,'undefined $HOME, IPython can not proceed.'
945 elif os.name == 'nt':
945 elif os.name == 'nt':
946 # For some strange reason, win9x returns 'nt' for os.name.
946 # For some strange reason, win9x returns 'nt' for os.name.
947 try:
947 try:
948 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
948 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
949 if not isdir(homedir):
949 if not isdir(homedir):
950 homedir = os.path.join(env['USERPROFILE'])
950 homedir = os.path.join(env['USERPROFILE'])
951 if not isdir(homedir):
951 if not isdir(homedir):
952 raise HomeDirError
952 raise HomeDirError
953 return homedir
953 return homedir
954 except KeyError:
954 except KeyError:
955 try:
955 try:
956 # Use the registry to get the 'My Documents' folder.
956 # Use the registry to get the 'My Documents' folder.
957 import _winreg as wreg
957 import _winreg as wreg
958 key = wreg.OpenKey(wreg.HKEY_CURRENT_USER,
958 key = wreg.OpenKey(wreg.HKEY_CURRENT_USER,
959 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
959 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
960 homedir = wreg.QueryValueEx(key,'Personal')[0]
960 homedir = wreg.QueryValueEx(key,'Personal')[0]
961 key.Close()
961 key.Close()
962 if not isdir(homedir):
962 if not isdir(homedir):
963 e = ('Invalid "Personal" folder registry key '
963 e = ('Invalid "Personal" folder registry key '
964 'typically "My Documents".\n'
964 'typically "My Documents".\n'
965 'Value: %s\n'
965 'Value: %s\n'
966 'This is not a valid directory on your system.' %
966 'This is not a valid directory on your system.' %
967 homedir)
967 homedir)
968 raise HomeDirError(e)
968 raise HomeDirError(e)
969 return homedir
969 return homedir
970 except HomeDirError:
970 except HomeDirError:
971 raise
971 raise
972 except:
972 except:
973 return 'C:\\'
973 return 'C:\\'
974 elif os.name == 'dos':
974 elif os.name == 'dos':
975 # Desperate, may do absurd things in classic MacOS. May work under DOS.
975 # Desperate, may do absurd things in classic MacOS. May work under DOS.
976 return 'C:\\'
976 return 'C:\\'
977 else:
977 else:
978 raise HomeDirError,'support for your operating system not implemented.'
978 raise HomeDirError,'support for your operating system not implemented.'
979
979
980
980
981 def get_ipython_dir():
981 def get_ipython_dir():
982 """Get the IPython directory for this platform and user.
982 """Get the IPython directory for this platform and user.
983
983
984 This uses the logic in `get_home_dir` to find the home directory
984 This uses the logic in `get_home_dir` to find the home directory
985 and the adds either .ipython or _ipython to the end of the path.
985 and the adds either .ipython or _ipython to the end of the path.
986 """
986 """
987 if os.name == 'posix':
987 if os.name == 'posix':
988 ipdir_def = '.ipython'
988 ipdir_def = '.ipython'
989 else:
989 else:
990 ipdir_def = '_ipython'
990 ipdir_def = '_ipython'
991 home_dir = get_home_dir()
991 home_dir = get_home_dir()
992 ipdir = os.path.abspath(os.environ.get('IPYTHONDIR',
992 ipdir = os.path.abspath(os.environ.get('IPYTHONDIR',
993 os.path.join(home_dir, ipdir_def)))
993 os.path.join(home_dir, ipdir_def)))
994 return ipdir.decode(sys.getfilesystemencoding())
994 return ipdir.decode(sys.getfilesystemencoding())
995
995
996 def get_security_dir():
996 def get_security_dir():
997 """Get the IPython security directory.
997 """Get the IPython security directory.
998
998
999 This directory is the default location for all security related files,
999 This directory is the default location for all security related files,
1000 including SSL/TLS certificates and FURL files.
1000 including SSL/TLS certificates and FURL files.
1001
1001
1002 If the directory does not exist, it is created with 0700 permissions.
1002 If the directory does not exist, it is created with 0700 permissions.
1003 If it exists, permissions are set to 0700.
1003 If it exists, permissions are set to 0700.
1004 """
1004 """
1005 security_dir = os.path.join(get_ipython_dir(), 'security')
1005 security_dir = os.path.join(get_ipython_dir(), 'security')
1006 if not os.path.isdir(security_dir):
1006 if not os.path.isdir(security_dir):
1007 os.mkdir(security_dir, 0700)
1007 os.mkdir(security_dir, 0700)
1008 else:
1008 else:
1009 os.chmod(security_dir, 0700)
1009 os.chmod(security_dir, 0700)
1010 return security_dir
1010 return security_dir
1011
1011
1012 def get_log_dir():
1012 def get_log_dir():
1013 """Get the IPython log directory.
1013 """Get the IPython log directory.
1014
1014
1015 If the log directory does not exist, it is created.
1015 If the log directory does not exist, it is created.
1016 """
1016 """
1017 log_dir = os.path.join(get_ipython_dir(), 'log')
1017 log_dir = os.path.join(get_ipython_dir(), 'log')
1018 if not os.path.isdir(log_dir):
1018 if not os.path.isdir(log_dir):
1019 os.mkdir(log_dir, 0777)
1019 os.mkdir(log_dir, 0777)
1020 return log_dir
1020 return log_dir
1021
1021
1022 #****************************************************************************
1022 #****************************************************************************
1023 # strings and text
1023 # strings and text
1024
1024
1025 class LSString(str):
1025 class LSString(str):
1026 """String derivative with a special access attributes.
1026 """String derivative with a special access attributes.
1027
1027
1028 These are normal strings, but with the special attributes:
1028 These are normal strings, but with the special attributes:
1029
1029
1030 .l (or .list) : value as list (split on newlines).
1030 .l (or .list) : value as list (split on newlines).
1031 .n (or .nlstr): original value (the string itself).
1031 .n (or .nlstr): original value (the string itself).
1032 .s (or .spstr): value as whitespace-separated string.
1032 .s (or .spstr): value as whitespace-separated string.
1033 .p (or .paths): list of path objects
1033 .p (or .paths): list of path objects
1034
1034
1035 Any values which require transformations are computed only once and
1035 Any values which require transformations are computed only once and
1036 cached.
1036 cached.
1037
1037
1038 Such strings are very useful to efficiently interact with the shell, which
1038 Such strings are very useful to efficiently interact with the shell, which
1039 typically only understands whitespace-separated options for commands."""
1039 typically only understands whitespace-separated options for commands."""
1040
1040
1041 def get_list(self):
1041 def get_list(self):
1042 try:
1042 try:
1043 return self.__list
1043 return self.__list
1044 except AttributeError:
1044 except AttributeError:
1045 self.__list = self.split('\n')
1045 self.__list = self.split('\n')
1046 return self.__list
1046 return self.__list
1047
1047
1048 l = list = property(get_list)
1048 l = list = property(get_list)
1049
1049
1050 def get_spstr(self):
1050 def get_spstr(self):
1051 try:
1051 try:
1052 return self.__spstr
1052 return self.__spstr
1053 except AttributeError:
1053 except AttributeError:
1054 self.__spstr = self.replace('\n',' ')
1054 self.__spstr = self.replace('\n',' ')
1055 return self.__spstr
1055 return self.__spstr
1056
1056
1057 s = spstr = property(get_spstr)
1057 s = spstr = property(get_spstr)
1058
1058
1059 def get_nlstr(self):
1059 def get_nlstr(self):
1060 return self
1060 return self
1061
1061
1062 n = nlstr = property(get_nlstr)
1062 n = nlstr = property(get_nlstr)
1063
1063
1064 def get_paths(self):
1064 def get_paths(self):
1065 try:
1065 try:
1066 return self.__paths
1066 return self.__paths
1067 except AttributeError:
1067 except AttributeError:
1068 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
1068 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
1069 return self.__paths
1069 return self.__paths
1070
1070
1071 p = paths = property(get_paths)
1071 p = paths = property(get_paths)
1072
1072
1073 def print_lsstring(arg):
1073 def print_lsstring(arg):
1074 """ Prettier (non-repr-like) and more informative printer for LSString """
1074 """ Prettier (non-repr-like) and more informative printer for LSString """
1075 print "LSString (.p, .n, .l, .s available). Value:"
1075 print "LSString (.p, .n, .l, .s available). Value:"
1076 print arg
1076 print arg
1077
1077
1078 print_lsstring = result_display.when_type(LSString)(print_lsstring)
1078 print_lsstring = result_display.when_type(LSString)(print_lsstring)
1079
1079
1080 #----------------------------------------------------------------------------
1080 #----------------------------------------------------------------------------
1081 class SList(list):
1081 class SList(list):
1082 """List derivative with a special access attributes.
1082 """List derivative with a special access attributes.
1083
1083
1084 These are normal lists, but with the special attributes:
1084 These are normal lists, but with the special attributes:
1085
1085
1086 .l (or .list) : value as list (the list itself).
1086 .l (or .list) : value as list (the list itself).
1087 .n (or .nlstr): value as a string, joined on newlines.
1087 .n (or .nlstr): value as a string, joined on newlines.
1088 .s (or .spstr): value as a string, joined on spaces.
1088 .s (or .spstr): value as a string, joined on spaces.
1089 .p (or .paths): list of path objects
1089 .p (or .paths): list of path objects
1090
1090
1091 Any values which require transformations are computed only once and
1091 Any values which require transformations are computed only once and
1092 cached."""
1092 cached."""
1093
1093
1094 def get_list(self):
1094 def get_list(self):
1095 return self
1095 return self
1096
1096
1097 l = list = property(get_list)
1097 l = list = property(get_list)
1098
1098
1099 def get_spstr(self):
1099 def get_spstr(self):
1100 try:
1100 try:
1101 return self.__spstr
1101 return self.__spstr
1102 except AttributeError:
1102 except AttributeError:
1103 self.__spstr = ' '.join(self)
1103 self.__spstr = ' '.join(self)
1104 return self.__spstr
1104 return self.__spstr
1105
1105
1106 s = spstr = property(get_spstr)
1106 s = spstr = property(get_spstr)
1107
1107
1108 def get_nlstr(self):
1108 def get_nlstr(self):
1109 try:
1109 try:
1110 return self.__nlstr
1110 return self.__nlstr
1111 except AttributeError:
1111 except AttributeError:
1112 self.__nlstr = '\n'.join(self)
1112 self.__nlstr = '\n'.join(self)
1113 return self.__nlstr
1113 return self.__nlstr
1114
1114
1115 n = nlstr = property(get_nlstr)
1115 n = nlstr = property(get_nlstr)
1116
1116
1117 def get_paths(self):
1117 def get_paths(self):
1118 try:
1118 try:
1119 return self.__paths
1119 return self.__paths
1120 except AttributeError:
1120 except AttributeError:
1121 self.__paths = [path(p) for p in self if os.path.exists(p)]
1121 self.__paths = [path(p) for p in self if os.path.exists(p)]
1122 return self.__paths
1122 return self.__paths
1123
1123
1124 p = paths = property(get_paths)
1124 p = paths = property(get_paths)
1125
1125
1126 def grep(self, pattern, prune = False, field = None):
1126 def grep(self, pattern, prune = False, field = None):
1127 """ Return all strings matching 'pattern' (a regex or callable)
1127 """ Return all strings matching 'pattern' (a regex or callable)
1128
1128
1129 This is case-insensitive. If prune is true, return all items
1129 This is case-insensitive. If prune is true, return all items
1130 NOT matching the pattern.
1130 NOT matching the pattern.
1131
1131
1132 If field is specified, the match must occur in the specified
1132 If field is specified, the match must occur in the specified
1133 whitespace-separated field.
1133 whitespace-separated field.
1134
1134
1135 Examples::
1135 Examples::
1136
1136
1137 a.grep( lambda x: x.startswith('C') )
1137 a.grep( lambda x: x.startswith('C') )
1138 a.grep('Cha.*log', prune=1)
1138 a.grep('Cha.*log', prune=1)
1139 a.grep('chm', field=-1)
1139 a.grep('chm', field=-1)
1140 """
1140 """
1141
1141
1142 def match_target(s):
1142 def match_target(s):
1143 if field is None:
1143 if field is None:
1144 return s
1144 return s
1145 parts = s.split()
1145 parts = s.split()
1146 try:
1146 try:
1147 tgt = parts[field]
1147 tgt = parts[field]
1148 return tgt
1148 return tgt
1149 except IndexError:
1149 except IndexError:
1150 return ""
1150 return ""
1151
1151
1152 if isinstance(pattern, basestring):
1152 if isinstance(pattern, basestring):
1153 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
1153 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
1154 else:
1154 else:
1155 pred = pattern
1155 pred = pattern
1156 if not prune:
1156 if not prune:
1157 return SList([el for el in self if pred(match_target(el))])
1157 return SList([el for el in self if pred(match_target(el))])
1158 else:
1158 else:
1159 return SList([el for el in self if not pred(match_target(el))])
1159 return SList([el for el in self if not pred(match_target(el))])
1160 def fields(self, *fields):
1160 def fields(self, *fields):
1161 """ Collect whitespace-separated fields from string list
1161 """ Collect whitespace-separated fields from string list
1162
1162
1163 Allows quick awk-like usage of string lists.
1163 Allows quick awk-like usage of string lists.
1164
1164
1165 Example data (in var a, created by 'a = !ls -l')::
1165 Example data (in var a, created by 'a = !ls -l')::
1166 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
1166 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
1167 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
1167 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
1168
1168
1169 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
1169 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
1170 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
1170 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
1171 (note the joining by space).
1171 (note the joining by space).
1172 a.fields(-1) is ['ChangeLog', 'IPython']
1172 a.fields(-1) is ['ChangeLog', 'IPython']
1173
1173
1174 IndexErrors are ignored.
1174 IndexErrors are ignored.
1175
1175
1176 Without args, fields() just split()'s the strings.
1176 Without args, fields() just split()'s the strings.
1177 """
1177 """
1178 if len(fields) == 0:
1178 if len(fields) == 0:
1179 return [el.split() for el in self]
1179 return [el.split() for el in self]
1180
1180
1181 res = SList()
1181 res = SList()
1182 for el in [f.split() for f in self]:
1182 for el in [f.split() for f in self]:
1183 lineparts = []
1183 lineparts = []
1184
1184
1185 for fd in fields:
1185 for fd in fields:
1186 try:
1186 try:
1187 lineparts.append(el[fd])
1187 lineparts.append(el[fd])
1188 except IndexError:
1188 except IndexError:
1189 pass
1189 pass
1190 if lineparts:
1190 if lineparts:
1191 res.append(" ".join(lineparts))
1191 res.append(" ".join(lineparts))
1192
1192
1193 return res
1193 return res
1194 def sort(self,field= None, nums = False):
1194 def sort(self,field= None, nums = False):
1195 """ sort by specified fields (see fields())
1195 """ sort by specified fields (see fields())
1196
1196
1197 Example::
1197 Example::
1198 a.sort(1, nums = True)
1198 a.sort(1, nums = True)
1199
1199
1200 Sorts a by second field, in numerical order (so that 21 > 3)
1200 Sorts a by second field, in numerical order (so that 21 > 3)
1201
1201
1202 """
1202 """
1203
1203
1204 #decorate, sort, undecorate
1204 #decorate, sort, undecorate
1205 if field is not None:
1205 if field is not None:
1206 dsu = [[SList([line]).fields(field), line] for line in self]
1206 dsu = [[SList([line]).fields(field), line] for line in self]
1207 else:
1207 else:
1208 dsu = [[line, line] for line in self]
1208 dsu = [[line, line] for line in self]
1209 if nums:
1209 if nums:
1210 for i in range(len(dsu)):
1210 for i in range(len(dsu)):
1211 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
1211 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
1212 try:
1212 try:
1213 n = int(numstr)
1213 n = int(numstr)
1214 except ValueError:
1214 except ValueError:
1215 n = 0;
1215 n = 0;
1216 dsu[i][0] = n
1216 dsu[i][0] = n
1217
1217
1218
1218
1219 dsu.sort()
1219 dsu.sort()
1220 return SList([t[1] for t in dsu])
1220 return SList([t[1] for t in dsu])
1221
1221
1222 def print_slist(arg):
1222 def print_slist(arg):
1223 """ Prettier (non-repr-like) and more informative printer for SList """
1223 """ Prettier (non-repr-like) and more informative printer for SList """
1224 print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
1224 print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
1225 if hasattr(arg, 'hideonce') and arg.hideonce:
1225 if hasattr(arg, 'hideonce') and arg.hideonce:
1226 arg.hideonce = False
1226 arg.hideonce = False
1227 return
1227 return
1228
1228
1229 nlprint(arg)
1229 nlprint(arg)
1230
1230
1231 print_slist = result_display.when_type(SList)(print_slist)
1231 print_slist = result_display.when_type(SList)(print_slist)
1232
1232
1233
1233
1234
1234
1235 #----------------------------------------------------------------------------
1235 #----------------------------------------------------------------------------
1236 def esc_quotes(strng):
1236 def esc_quotes(strng):
1237 """Return the input string with single and double quotes escaped out"""
1237 """Return the input string with single and double quotes escaped out"""
1238
1238
1239 return strng.replace('"','\\"').replace("'","\\'")
1239 return strng.replace('"','\\"').replace("'","\\'")
1240
1240
1241 #----------------------------------------------------------------------------
1241 #----------------------------------------------------------------------------
1242 def make_quoted_expr(s):
1242 def make_quoted_expr(s):
1243 """Return string s in appropriate quotes, using raw string if possible.
1243 """Return string s in appropriate quotes, using raw string if possible.
1244
1244
1245 XXX - example removed because it caused encoding errors in documentation
1245 XXX - example removed because it caused encoding errors in documentation
1246 generation. We need a new example that doesn't contain invalid chars.
1246 generation. We need a new example that doesn't contain invalid chars.
1247
1247
1248 Note the use of raw string and padding at the end to allow trailing
1248 Note the use of raw string and padding at the end to allow trailing
1249 backslash.
1249 backslash.
1250 """
1250 """
1251
1251
1252 tail = ''
1252 tail = ''
1253 tailpadding = ''
1253 tailpadding = ''
1254 raw = ''
1254 raw = ''
1255 if "\\" in s:
1255 if "\\" in s:
1256 raw = 'r'
1256 raw = 'r'
1257 if s.endswith('\\'):
1257 if s.endswith('\\'):
1258 tail = '[:-1]'
1258 tail = '[:-1]'
1259 tailpadding = '_'
1259 tailpadding = '_'
1260 if '"' not in s:
1260 if '"' not in s:
1261 quote = '"'
1261 quote = '"'
1262 elif "'" not in s:
1262 elif "'" not in s:
1263 quote = "'"
1263 quote = "'"
1264 elif '"""' not in s and not s.endswith('"'):
1264 elif '"""' not in s and not s.endswith('"'):
1265 quote = '"""'
1265 quote = '"""'
1266 elif "'''" not in s and not s.endswith("'"):
1266 elif "'''" not in s and not s.endswith("'"):
1267 quote = "'''"
1267 quote = "'''"
1268 else:
1268 else:
1269 # give up, backslash-escaped string will do
1269 # give up, backslash-escaped string will do
1270 return '"%s"' % esc_quotes(s)
1270 return '"%s"' % esc_quotes(s)
1271 res = raw + quote + s + tailpadding + quote + tail
1271 res = raw + quote + s + tailpadding + quote + tail
1272 return res
1272 return res
1273
1273
1274
1274
1275 #----------------------------------------------------------------------------
1275 #----------------------------------------------------------------------------
1276 def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'):
1276 def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'):
1277 """Take multiple lines of input.
1277 """Take multiple lines of input.
1278
1278
1279 A list with each line of input as a separate element is returned when a
1279 A list with each line of input as a separate element is returned when a
1280 termination string is entered (defaults to a single '.'). Input can also
1280 termination string is entered (defaults to a single '.'). Input can also
1281 terminate via EOF (^D in Unix, ^Z-RET in Windows).
1281 terminate via EOF (^D in Unix, ^Z-RET in Windows).
1282
1282
1283 Lines of input which end in \\ are joined into single entries (and a
1283 Lines of input which end in \\ are joined into single entries (and a
1284 secondary continuation prompt is issued as long as the user terminates
1284 secondary continuation prompt is issued as long as the user terminates
1285 lines with \\). This allows entering very long strings which are still
1285 lines with \\). This allows entering very long strings which are still
1286 meant to be treated as single entities.
1286 meant to be treated as single entities.
1287 """
1287 """
1288
1288
1289 try:
1289 try:
1290 if header:
1290 if header:
1291 header += '\n'
1291 header += '\n'
1292 lines = [raw_input(header + ps1)]
1292 lines = [raw_input(header + ps1)]
1293 except EOFError:
1293 except EOFError:
1294 return []
1294 return []
1295 terminate = [terminate_str]
1295 terminate = [terminate_str]
1296 try:
1296 try:
1297 while lines[-1:] != terminate:
1297 while lines[-1:] != terminate:
1298 new_line = raw_input(ps1)
1298 new_line = raw_input(ps1)
1299 while new_line.endswith('\\'):
1299 while new_line.endswith('\\'):
1300 new_line = new_line[:-1] + raw_input(ps2)
1300 new_line = new_line[:-1] + raw_input(ps2)
1301 lines.append(new_line)
1301 lines.append(new_line)
1302
1302
1303 return lines[:-1] # don't return the termination command
1303 return lines[:-1] # don't return the termination command
1304 except EOFError:
1304 except EOFError:
1305 print
1305 print
1306 return lines
1306 return lines
1307
1307
1308 #----------------------------------------------------------------------------
1308 #----------------------------------------------------------------------------
1309 def raw_input_ext(prompt='', ps2='... '):
1309 def raw_input_ext(prompt='', ps2='... '):
1310 """Similar to raw_input(), but accepts extended lines if input ends with \\."""
1310 """Similar to raw_input(), but accepts extended lines if input ends with \\."""
1311
1311
1312 line = raw_input(prompt)
1312 line = raw_input(prompt)
1313 while line.endswith('\\'):
1313 while line.endswith('\\'):
1314 line = line[:-1] + raw_input(ps2)
1314 line = line[:-1] + raw_input(ps2)
1315 return line
1315 return line
1316
1316
1317 #----------------------------------------------------------------------------
1317 #----------------------------------------------------------------------------
1318 def ask_yes_no(prompt,default=None):
1318 def ask_yes_no(prompt,default=None):
1319 """Asks a question and returns a boolean (y/n) answer.
1319 """Asks a question and returns a boolean (y/n) answer.
1320
1320
1321 If default is given (one of 'y','n'), it is used if the user input is
1321 If default is given (one of 'y','n'), it is used if the user input is
1322 empty. Otherwise the question is repeated until an answer is given.
1322 empty. Otherwise the question is repeated until an answer is given.
1323
1323
1324 An EOF is treated as the default answer. If there is no default, an
1324 An EOF is treated as the default answer. If there is no default, an
1325 exception is raised to prevent infinite loops.
1325 exception is raised to prevent infinite loops.
1326
1326
1327 Valid answers are: y/yes/n/no (match is not case sensitive)."""
1327 Valid answers are: y/yes/n/no (match is not case sensitive)."""
1328
1328
1329 answers = {'y':True,'n':False,'yes':True,'no':False}
1329 answers = {'y':True,'n':False,'yes':True,'no':False}
1330 ans = None
1330 ans = None
1331 while ans not in answers.keys():
1331 while ans not in answers.keys():
1332 try:
1332 try:
1333 ans = raw_input(prompt+' ').lower()
1333 ans = raw_input(prompt+' ').lower()
1334 if not ans: # response was an empty string
1334 if not ans: # response was an empty string
1335 ans = default
1335 ans = default
1336 except KeyboardInterrupt:
1336 except KeyboardInterrupt:
1337 pass
1337 pass
1338 except EOFError:
1338 except EOFError:
1339 if default in answers.keys():
1339 if default in answers.keys():
1340 ans = default
1340 ans = default
1341 print
1341 print
1342 else:
1342 else:
1343 raise
1343 raise
1344
1344
1345 return answers[ans]
1345 return answers[ans]
1346
1346
1347 #----------------------------------------------------------------------------
1347 #----------------------------------------------------------------------------
1348 def marquee(txt='',width=78,mark='*'):
1348 def marquee(txt='',width=78,mark='*'):
1349 """Return the input string centered in a 'marquee'."""
1349 """Return the input string centered in a 'marquee'."""
1350 if not txt:
1350 if not txt:
1351 return (mark*width)[:width]
1351 return (mark*width)[:width]
1352 nmark = (width-len(txt)-2)/len(mark)/2
1352 nmark = (width-len(txt)-2)/len(mark)/2
1353 if nmark < 0: nmark =0
1353 if nmark < 0: nmark =0
1354 marks = mark*nmark
1354 marks = mark*nmark
1355 return '%s %s %s' % (marks,txt,marks)
1355 return '%s %s %s' % (marks,txt,marks)
1356
1356
1357 #----------------------------------------------------------------------------
1357 #----------------------------------------------------------------------------
1358 class EvalDict:
1358 class EvalDict:
1359 """
1359 """
1360 Emulate a dict which evaluates its contents in the caller's frame.
1360 Emulate a dict which evaluates its contents in the caller's frame.
1361
1361
1362 Usage:
1362 Usage:
1363 >>> number = 19
1363 >>> number = 19
1364
1364
1365 >>> text = "python"
1365 >>> text = "python"
1366
1366
1367 >>> print "%(text.capitalize())s %(number/9.0).1f rules!" % EvalDict()
1367 >>> print "%(text.capitalize())s %(number/9.0).1f rules!" % EvalDict()
1368 Python 2.1 rules!
1368 Python 2.1 rules!
1369 """
1369 """
1370
1370
1371 # This version is due to sismex01@hebmex.com on c.l.py, and is basically a
1371 # This version is due to sismex01@hebmex.com on c.l.py, and is basically a
1372 # modified (shorter) version of:
1372 # modified (shorter) version of:
1373 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66018 by
1373 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66018 by
1374 # Skip Montanaro (skip@pobox.com).
1374 # Skip Montanaro (skip@pobox.com).
1375
1375
1376 def __getitem__(self, name):
1376 def __getitem__(self, name):
1377 frame = sys._getframe(1)
1377 frame = sys._getframe(1)
1378 return eval(name, frame.f_globals, frame.f_locals)
1378 return eval(name, frame.f_globals, frame.f_locals)
1379
1379
1380 EvalString = EvalDict # for backwards compatibility
1380 EvalString = EvalDict # for backwards compatibility
1381 #----------------------------------------------------------------------------
1381 #----------------------------------------------------------------------------
1382 def qw(words,flat=0,sep=None,maxsplit=-1):
1382 def qw(words,flat=0,sep=None,maxsplit=-1):
1383 """Similar to Perl's qw() operator, but with some more options.
1383 """Similar to Perl's qw() operator, but with some more options.
1384
1384
1385 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
1385 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
1386
1386
1387 words can also be a list itself, and with flat=1, the output will be
1387 words can also be a list itself, and with flat=1, the output will be
1388 recursively flattened.
1388 recursively flattened.
1389
1389
1390 Examples:
1390 Examples:
1391
1391
1392 >>> qw('1 2')
1392 >>> qw('1 2')
1393 ['1', '2']
1393 ['1', '2']
1394
1394
1395 >>> qw(['a b','1 2',['m n','p q']])
1395 >>> qw(['a b','1 2',['m n','p q']])
1396 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
1396 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
1397
1397
1398 >>> qw(['a b','1 2',['m n','p q']],flat=1)
1398 >>> qw(['a b','1 2',['m n','p q']],flat=1)
1399 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q']
1399 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q']
1400 """
1400 """
1401
1401
1402 if type(words) in StringTypes:
1402 if type(words) in StringTypes:
1403 return [word.strip() for word in words.split(sep,maxsplit)
1403 return [word.strip() for word in words.split(sep,maxsplit)
1404 if word and not word.isspace() ]
1404 if word and not word.isspace() ]
1405 if flat:
1405 if flat:
1406 return flatten(map(qw,words,[1]*len(words)))
1406 return flatten(map(qw,words,[1]*len(words)))
1407 return map(qw,words)
1407 return map(qw,words)
1408
1408
1409 #----------------------------------------------------------------------------
1409 #----------------------------------------------------------------------------
1410 def qwflat(words,sep=None,maxsplit=-1):
1410 def qwflat(words,sep=None,maxsplit=-1):
1411 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
1411 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
1412 return qw(words,1,sep,maxsplit)
1412 return qw(words,1,sep,maxsplit)
1413
1413
1414 #----------------------------------------------------------------------------
1414 #----------------------------------------------------------------------------
1415 def qw_lol(indata):
1415 def qw_lol(indata):
1416 """qw_lol('a b') -> [['a','b']],
1416 """qw_lol('a b') -> [['a','b']],
1417 otherwise it's just a call to qw().
1417 otherwise it's just a call to qw().
1418
1418
1419 We need this to make sure the modules_some keys *always* end up as a
1419 We need this to make sure the modules_some keys *always* end up as a
1420 list of lists."""
1420 list of lists."""
1421
1421
1422 if type(indata) in StringTypes:
1422 if type(indata) in StringTypes:
1423 return [qw(indata)]
1423 return [qw(indata)]
1424 else:
1424 else:
1425 return qw(indata)
1425 return qw(indata)
1426
1426
1427 #----------------------------------------------------------------------------
1427 #----------------------------------------------------------------------------
1428 def grep(pat,list,case=1):
1428 def grep(pat,list,case=1):
1429 """Simple minded grep-like function.
1429 """Simple minded grep-like function.
1430 grep(pat,list) returns occurrences of pat in list, None on failure.
1430 grep(pat,list) returns occurrences of pat in list, None on failure.
1431
1431
1432 It only does simple string matching, with no support for regexps. Use the
1432 It only does simple string matching, with no support for regexps. Use the
1433 option case=0 for case-insensitive matching."""
1433 option case=0 for case-insensitive matching."""
1434
1434
1435 # This is pretty crude. At least it should implement copying only references
1435 # This is pretty crude. At least it should implement copying only references
1436 # to the original data in case it's big. Now it copies the data for output.
1436 # to the original data in case it's big. Now it copies the data for output.
1437 out=[]
1437 out=[]
1438 if case:
1438 if case:
1439 for term in list:
1439 for term in list:
1440 if term.find(pat)>-1: out.append(term)
1440 if term.find(pat)>-1: out.append(term)
1441 else:
1441 else:
1442 lpat=pat.lower()
1442 lpat=pat.lower()
1443 for term in list:
1443 for term in list:
1444 if term.lower().find(lpat)>-1: out.append(term)
1444 if term.lower().find(lpat)>-1: out.append(term)
1445
1445
1446 if len(out): return out
1446 if len(out): return out
1447 else: return None
1447 else: return None
1448
1448
1449 #----------------------------------------------------------------------------
1449 #----------------------------------------------------------------------------
1450 def dgrep(pat,*opts):
1450 def dgrep(pat,*opts):
1451 """Return grep() on dir()+dir(__builtins__).
1451 """Return grep() on dir()+dir(__builtins__).
1452
1452
1453 A very common use of grep() when working interactively."""
1453 A very common use of grep() when working interactively."""
1454
1454
1455 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
1455 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
1456
1456
1457 #----------------------------------------------------------------------------
1457 #----------------------------------------------------------------------------
1458 def idgrep(pat):
1458 def idgrep(pat):
1459 """Case-insensitive dgrep()"""
1459 """Case-insensitive dgrep()"""
1460
1460
1461 return dgrep(pat,0)
1461 return dgrep(pat,0)
1462
1462
1463 #----------------------------------------------------------------------------
1463 #----------------------------------------------------------------------------
1464 def igrep(pat,list):
1464 def igrep(pat,list):
1465 """Synonym for case-insensitive grep."""
1465 """Synonym for case-insensitive grep."""
1466
1466
1467 return grep(pat,list,case=0)
1467 return grep(pat,list,case=0)
1468
1468
1469 #----------------------------------------------------------------------------
1469 #----------------------------------------------------------------------------
1470 def indent(str,nspaces=4,ntabs=0):
1470 def indent(str,nspaces=4,ntabs=0):
1471 """Indent a string a given number of spaces or tabstops.
1471 """Indent a string a given number of spaces or tabstops.
1472
1472
1473 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
1473 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
1474 """
1474 """
1475 if str is None:
1475 if str is None:
1476 return
1476 return
1477 ind = '\t'*ntabs+' '*nspaces
1477 ind = '\t'*ntabs+' '*nspaces
1478 outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind))
1478 outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind))
1479 if outstr.endswith(os.linesep+ind):
1479 if outstr.endswith(os.linesep+ind):
1480 return outstr[:-len(ind)]
1480 return outstr[:-len(ind)]
1481 else:
1481 else:
1482 return outstr
1482 return outstr
1483
1483
1484 #-----------------------------------------------------------------------------
1484 #-----------------------------------------------------------------------------
1485 def native_line_ends(filename,backup=1):
1485 def native_line_ends(filename,backup=1):
1486 """Convert (in-place) a file to line-ends native to the current OS.
1486 """Convert (in-place) a file to line-ends native to the current OS.
1487
1487
1488 If the optional backup argument is given as false, no backup of the
1488 If the optional backup argument is given as false, no backup of the
1489 original file is left. """
1489 original file is left. """
1490
1490
1491 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
1491 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
1492
1492
1493 bak_filename = filename + backup_suffixes[os.name]
1493 bak_filename = filename + backup_suffixes[os.name]
1494
1494
1495 original = open(filename).read()
1495 original = open(filename).read()
1496 shutil.copy2(filename,bak_filename)
1496 shutil.copy2(filename,bak_filename)
1497 try:
1497 try:
1498 new = open(filename,'wb')
1498 new = open(filename,'wb')
1499 new.write(os.linesep.join(original.splitlines()))
1499 new.write(os.linesep.join(original.splitlines()))
1500 new.write(os.linesep) # ALWAYS put an eol at the end of the file
1500 new.write(os.linesep) # ALWAYS put an eol at the end of the file
1501 new.close()
1501 new.close()
1502 except:
1502 except:
1503 os.rename(bak_filename,filename)
1503 os.rename(bak_filename,filename)
1504 if not backup:
1504 if not backup:
1505 try:
1505 try:
1506 os.remove(bak_filename)
1506 os.remove(bak_filename)
1507 except:
1507 except:
1508 pass
1508 pass
1509
1509
1510 #----------------------------------------------------------------------------
1510 #----------------------------------------------------------------------------
1511 def get_pager_cmd(pager_cmd = None):
1511 def get_pager_cmd(pager_cmd = None):
1512 """Return a pager command.
1512 """Return a pager command.
1513
1513
1514 Makes some attempts at finding an OS-correct one."""
1514 Makes some attempts at finding an OS-correct one."""
1515
1515
1516 if os.name == 'posix':
1516 if os.name == 'posix':
1517 default_pager_cmd = 'less -r' # -r for color control sequences
1517 default_pager_cmd = 'less -r' # -r for color control sequences
1518 elif os.name in ['nt','dos']:
1518 elif os.name in ['nt','dos']:
1519 default_pager_cmd = 'type'
1519 default_pager_cmd = 'type'
1520
1520
1521 if pager_cmd is None:
1521 if pager_cmd is None:
1522 try:
1522 try:
1523 pager_cmd = os.environ['PAGER']
1523 pager_cmd = os.environ['PAGER']
1524 except:
1524 except:
1525 pager_cmd = default_pager_cmd
1525 pager_cmd = default_pager_cmd
1526 return pager_cmd
1526 return pager_cmd
1527
1527
1528 #-----------------------------------------------------------------------------
1528 #-----------------------------------------------------------------------------
1529 def get_pager_start(pager,start):
1529 def get_pager_start(pager,start):
1530 """Return the string for paging files with an offset.
1530 """Return the string for paging files with an offset.
1531
1531
1532 This is the '+N' argument which less and more (under Unix) accept.
1532 This is the '+N' argument which less and more (under Unix) accept.
1533 """
1533 """
1534
1534
1535 if pager in ['less','more']:
1535 if pager in ['less','more']:
1536 if start:
1536 if start:
1537 start_string = '+' + str(start)
1537 start_string = '+' + str(start)
1538 else:
1538 else:
1539 start_string = ''
1539 start_string = ''
1540 else:
1540 else:
1541 start_string = ''
1541 start_string = ''
1542 return start_string
1542 return start_string
1543
1543
1544 #----------------------------------------------------------------------------
1544 #----------------------------------------------------------------------------
1545 # (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch()
1545 # (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch()
1546 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
1546 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
1547 import msvcrt
1547 import msvcrt
1548 def page_more():
1548 def page_more():
1549 """ Smart pausing between pages
1549 """ Smart pausing between pages
1550
1550
1551 @return: True if need print more lines, False if quit
1551 @return: True if need print more lines, False if quit
1552 """
1552 """
1553 Term.cout.write('---Return to continue, q to quit--- ')
1553 Term.cout.write('---Return to continue, q to quit--- ')
1554 ans = msvcrt.getch()
1554 ans = msvcrt.getch()
1555 if ans in ("q", "Q"):
1555 if ans in ("q", "Q"):
1556 result = False
1556 result = False
1557 else:
1557 else:
1558 result = True
1558 result = True
1559 Term.cout.write("\b"*37 + " "*37 + "\b"*37)
1559 Term.cout.write("\b"*37 + " "*37 + "\b"*37)
1560 return result
1560 return result
1561 else:
1561 else:
1562 def page_more():
1562 def page_more():
1563 ans = raw_input('---Return to continue, q to quit--- ')
1563 ans = raw_input('---Return to continue, q to quit--- ')
1564 if ans.lower().startswith('q'):
1564 if ans.lower().startswith('q'):
1565 return False
1565 return False
1566 else:
1566 else:
1567 return True
1567 return True
1568
1568
1569 esc_re = re.compile(r"(\x1b[^m]+m)")
1569 esc_re = re.compile(r"(\x1b[^m]+m)")
1570
1570
1571 def page_dumb(strng,start=0,screen_lines=25):
1571 def page_dumb(strng,start=0,screen_lines=25):
1572 """Very dumb 'pager' in Python, for when nothing else works.
1572 """Very dumb 'pager' in Python, for when nothing else works.
1573
1573
1574 Only moves forward, same interface as page(), except for pager_cmd and
1574 Only moves forward, same interface as page(), except for pager_cmd and
1575 mode."""
1575 mode."""
1576
1576
1577 out_ln = strng.splitlines()[start:]
1577 out_ln = strng.splitlines()[start:]
1578 screens = chop(out_ln,screen_lines-1)
1578 screens = chop(out_ln,screen_lines-1)
1579 if len(screens) == 1:
1579 if len(screens) == 1:
1580 print >>Term.cout, os.linesep.join(screens[0])
1580 print >>Term.cout, os.linesep.join(screens[0])
1581 else:
1581 else:
1582 last_escape = ""
1582 last_escape = ""
1583 for scr in screens[0:-1]:
1583 for scr in screens[0:-1]:
1584 hunk = os.linesep.join(scr)
1584 hunk = os.linesep.join(scr)
1585 print >>Term.cout, last_escape + hunk
1585 print >>Term.cout, last_escape + hunk
1586 if not page_more():
1586 if not page_more():
1587 return
1587 return
1588 esc_list = esc_re.findall(hunk)
1588 esc_list = esc_re.findall(hunk)
1589 if len(esc_list) > 0:
1589 if len(esc_list) > 0:
1590 last_escape = esc_list[-1]
1590 last_escape = esc_list[-1]
1591 print >>Term.cout, last_escape + os.linesep.join(screens[-1])
1591 print >>Term.cout, last_escape + os.linesep.join(screens[-1])
1592
1592
1593 #----------------------------------------------------------------------------
1593 #----------------------------------------------------------------------------
1594 def page(strng,start=0,screen_lines=0,pager_cmd = None):
1594 def page(strng,start=0,screen_lines=0,pager_cmd = None):
1595 """Print a string, piping through a pager after a certain length.
1595 """Print a string, piping through a pager after a certain length.
1596
1596
1597 The screen_lines parameter specifies the number of *usable* lines of your
1597 The screen_lines parameter specifies the number of *usable* lines of your
1598 terminal screen (total lines minus lines you need to reserve to show other
1598 terminal screen (total lines minus lines you need to reserve to show other
1599 information).
1599 information).
1600
1600
1601 If you set screen_lines to a number <=0, page() will try to auto-determine
1601 If you set screen_lines to a number <=0, page() will try to auto-determine
1602 your screen size and will only use up to (screen_size+screen_lines) for
1602 your screen size and will only use up to (screen_size+screen_lines) for
1603 printing, paging after that. That is, if you want auto-detection but need
1603 printing, paging after that. That is, if you want auto-detection but need
1604 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
1604 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
1605 auto-detection without any lines reserved simply use screen_lines = 0.
1605 auto-detection without any lines reserved simply use screen_lines = 0.
1606
1606
1607 If a string won't fit in the allowed lines, it is sent through the
1607 If a string won't fit in the allowed lines, it is sent through the
1608 specified pager command. If none given, look for PAGER in the environment,
1608 specified pager command. If none given, look for PAGER in the environment,
1609 and ultimately default to less.
1609 and ultimately default to less.
1610
1610
1611 If no system pager works, the string is sent through a 'dumb pager'
1611 If no system pager works, the string is sent through a 'dumb pager'
1612 written in python, very simplistic.
1612 written in python, very simplistic.
1613 """
1613 """
1614
1614
1615 # Some routines may auto-compute start offsets incorrectly and pass a
1615 # Some routines may auto-compute start offsets incorrectly and pass a
1616 # negative value. Offset to 0 for robustness.
1616 # negative value. Offset to 0 for robustness.
1617 start = max(0,start)
1617 start = max(0,start)
1618
1618
1619 # first, try the hook
1619 # first, try the hook
1620 ip = ipapi.get()
1620 ip = ipapi.get()
1621 if ip:
1621 if ip:
1622 try:
1622 try:
1623 ip.IP.hooks.show_in_pager(strng)
1623 ip.IP.hooks.show_in_pager(strng)
1624 return
1624 return
1625 except ipapi.TryNext:
1625 except ipapi.TryNext:
1626 pass
1626 pass
1627
1627
1628 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
1628 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
1629 TERM = os.environ.get('TERM','dumb')
1629 TERM = os.environ.get('TERM','dumb')
1630 if TERM in ['dumb','emacs'] and os.name != 'nt':
1630 if TERM in ['dumb','emacs'] and os.name != 'nt':
1631 print strng
1631 print strng
1632 return
1632 return
1633 # chop off the topmost part of the string we don't want to see
1633 # chop off the topmost part of the string we don't want to see
1634 str_lines = strng.split(os.linesep)[start:]
1634 str_lines = strng.split(os.linesep)[start:]
1635 str_toprint = os.linesep.join(str_lines)
1635 str_toprint = os.linesep.join(str_lines)
1636 num_newlines = len(str_lines)
1636 num_newlines = len(str_lines)
1637 len_str = len(str_toprint)
1637 len_str = len(str_toprint)
1638
1638
1639 # Dumb heuristics to guesstimate number of on-screen lines the string
1639 # Dumb heuristics to guesstimate number of on-screen lines the string
1640 # takes. Very basic, but good enough for docstrings in reasonable
1640 # takes. Very basic, but good enough for docstrings in reasonable
1641 # terminals. If someone later feels like refining it, it's not hard.
1641 # terminals. If someone later feels like refining it, it's not hard.
1642 numlines = max(num_newlines,int(len_str/80)+1)
1642 numlines = max(num_newlines,int(len_str/80)+1)
1643
1643
1644 if os.name == "nt":
1644 if os.name == "nt":
1645 screen_lines_def = get_console_size(defaulty=25)[1]
1645 screen_lines_def = get_console_size(defaulty=25)[1]
1646 else:
1646 else:
1647 screen_lines_def = 25 # default value if we can't auto-determine
1647 screen_lines_def = 25 # default value if we can't auto-determine
1648
1648
1649 # auto-determine screen size
1649 # auto-determine screen size
1650 if screen_lines <= 0:
1650 if screen_lines <= 0:
1651 if TERM=='xterm':
1651 if TERM=='xterm':
1652 use_curses = USE_CURSES
1652 use_curses = USE_CURSES
1653 else:
1653 else:
1654 # curses causes problems on many terminals other than xterm.
1654 # curses causes problems on many terminals other than xterm.
1655 use_curses = False
1655 use_curses = False
1656 if use_curses:
1656 if use_curses:
1657 # There is a bug in curses, where *sometimes* it fails to properly
1657 # There is a bug in curses, where *sometimes* it fails to properly
1658 # initialize, and then after the endwin() call is made, the
1658 # initialize, and then after the endwin() call is made, the
1659 # terminal is left in an unusable state. Rather than trying to
1659 # terminal is left in an unusable state. Rather than trying to
1660 # check everytime for this (by requesting and comparing termios
1660 # check everytime for this (by requesting and comparing termios
1661 # flags each time), we just save the initial terminal state and
1661 # flags each time), we just save the initial terminal state and
1662 # unconditionally reset it every time. It's cheaper than making
1662 # unconditionally reset it every time. It's cheaper than making
1663 # the checks.
1663 # the checks.
1664 term_flags = termios.tcgetattr(sys.stdout)
1664 term_flags = termios.tcgetattr(sys.stdout)
1665 scr = curses.initscr()
1665 scr = curses.initscr()
1666 screen_lines_real,screen_cols = scr.getmaxyx()
1666 screen_lines_real,screen_cols = scr.getmaxyx()
1667 curses.endwin()
1667 curses.endwin()
1668 # Restore terminal state in case endwin() didn't.
1668 # Restore terminal state in case endwin() didn't.
1669 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
1669 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
1670 # Now we have what we needed: the screen size in rows/columns
1670 # Now we have what we needed: the screen size in rows/columns
1671 screen_lines += screen_lines_real
1671 screen_lines += screen_lines_real
1672 #print '***Screen size:',screen_lines_real,'lines x',\
1672 #print '***Screen size:',screen_lines_real,'lines x',\
1673 #screen_cols,'columns.' # dbg
1673 #screen_cols,'columns.' # dbg
1674 else:
1674 else:
1675 screen_lines += screen_lines_def
1675 screen_lines += screen_lines_def
1676
1676
1677 #print 'numlines',numlines,'screenlines',screen_lines # dbg
1677 #print 'numlines',numlines,'screenlines',screen_lines # dbg
1678 if numlines <= screen_lines :
1678 if numlines <= screen_lines :
1679 #print '*** normal print' # dbg
1679 #print '*** normal print' # dbg
1680 print >>Term.cout, str_toprint
1680 print >>Term.cout, str_toprint
1681 else:
1681 else:
1682 # Try to open pager and default to internal one if that fails.
1682 # Try to open pager and default to internal one if that fails.
1683 # All failure modes are tagged as 'retval=1', to match the return
1683 # All failure modes are tagged as 'retval=1', to match the return
1684 # value of a failed system command. If any intermediate attempt
1684 # value of a failed system command. If any intermediate attempt
1685 # sets retval to 1, at the end we resort to our own page_dumb() pager.
1685 # sets retval to 1, at the end we resort to our own page_dumb() pager.
1686 pager_cmd = get_pager_cmd(pager_cmd)
1686 pager_cmd = get_pager_cmd(pager_cmd)
1687 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1687 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1688 if os.name == 'nt':
1688 if os.name == 'nt':
1689 if pager_cmd.startswith('type'):
1689 if pager_cmd.startswith('type'):
1690 # The default WinXP 'type' command is failing on complex strings.
1690 # The default WinXP 'type' command is failing on complex strings.
1691 retval = 1
1691 retval = 1
1692 else:
1692 else:
1693 tmpname = tempfile.mktemp('.txt')
1693 tmpname = tempfile.mktemp('.txt')
1694 tmpfile = file(tmpname,'wt')
1694 tmpfile = file(tmpname,'wt')
1695 tmpfile.write(strng)
1695 tmpfile.write(strng)
1696 tmpfile.close()
1696 tmpfile.close()
1697 cmd = "%s < %s" % (pager_cmd,tmpname)
1697 cmd = "%s < %s" % (pager_cmd,tmpname)
1698 if os.system(cmd):
1698 if os.system(cmd):
1699 retval = 1
1699 retval = 1
1700 else:
1700 else:
1701 retval = None
1701 retval = None
1702 os.remove(tmpname)
1702 os.remove(tmpname)
1703 else:
1703 else:
1704 try:
1704 try:
1705 retval = None
1705 retval = None
1706 # if I use popen4, things hang. No idea why.
1706 # if I use popen4, things hang. No idea why.
1707 #pager,shell_out = os.popen4(pager_cmd)
1707 #pager,shell_out = os.popen4(pager_cmd)
1708 pager = os.popen(pager_cmd,'w')
1708 pager = os.popen(pager_cmd,'w')
1709 pager.write(strng)
1709 pager.write(strng)
1710 pager.close()
1710 pager.close()
1711 retval = pager.close() # success returns None
1711 retval = pager.close() # success returns None
1712 except IOError,msg: # broken pipe when user quits
1712 except IOError,msg: # broken pipe when user quits
1713 if msg.args == (32,'Broken pipe'):
1713 if msg.args == (32,'Broken pipe'):
1714 retval = None
1714 retval = None
1715 else:
1715 else:
1716 retval = 1
1716 retval = 1
1717 except OSError:
1717 except OSError:
1718 # Other strange problems, sometimes seen in Win2k/cygwin
1718 # Other strange problems, sometimes seen in Win2k/cygwin
1719 retval = 1
1719 retval = 1
1720 if retval is not None:
1720 if retval is not None:
1721 page_dumb(strng,screen_lines=screen_lines)
1721 page_dumb(strng,screen_lines=screen_lines)
1722
1722
1723 #----------------------------------------------------------------------------
1723 #----------------------------------------------------------------------------
1724 def page_file(fname,start = 0, pager_cmd = None):
1724 def page_file(fname,start = 0, pager_cmd = None):
1725 """Page a file, using an optional pager command and starting line.
1725 """Page a file, using an optional pager command and starting line.
1726 """
1726 """
1727
1727
1728 pager_cmd = get_pager_cmd(pager_cmd)
1728 pager_cmd = get_pager_cmd(pager_cmd)
1729 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1729 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1730
1730
1731 try:
1731 try:
1732 if os.environ['TERM'] in ['emacs','dumb']:
1732 if os.environ['TERM'] in ['emacs','dumb']:
1733 raise EnvironmentError
1733 raise EnvironmentError
1734 xsys(pager_cmd + ' ' + fname)
1734 xsys(pager_cmd + ' ' + fname)
1735 except:
1735 except:
1736 try:
1736 try:
1737 if start > 0:
1737 if start > 0:
1738 start -= 1
1738 start -= 1
1739 page(open(fname).read(),start)
1739 page(open(fname).read(),start)
1740 except:
1740 except:
1741 print 'Unable to show file',`fname`
1741 print 'Unable to show file',`fname`
1742
1742
1743
1743
1744 #----------------------------------------------------------------------------
1744 #----------------------------------------------------------------------------
1745 def snip_print(str,width = 75,print_full = 0,header = ''):
1745 def snip_print(str,width = 75,print_full = 0,header = ''):
1746 """Print a string snipping the midsection to fit in width.
1746 """Print a string snipping the midsection to fit in width.
1747
1747
1748 print_full: mode control:
1748 print_full: mode control:
1749 - 0: only snip long strings
1749 - 0: only snip long strings
1750 - 1: send to page() directly.
1750 - 1: send to page() directly.
1751 - 2: snip long strings and ask for full length viewing with page()
1751 - 2: snip long strings and ask for full length viewing with page()
1752 Return 1 if snipping was necessary, 0 otherwise."""
1752 Return 1 if snipping was necessary, 0 otherwise."""
1753
1753
1754 if print_full == 1:
1754 if print_full == 1:
1755 page(header+str)
1755 page(header+str)
1756 return 0
1756 return 0
1757
1757
1758 print header,
1758 print header,
1759 if len(str) < width:
1759 if len(str) < width:
1760 print str
1760 print str
1761 snip = 0
1761 snip = 0
1762 else:
1762 else:
1763 whalf = int((width -5)/2)
1763 whalf = int((width -5)/2)
1764 print str[:whalf] + ' <...> ' + str[-whalf:]
1764 print str[:whalf] + ' <...> ' + str[-whalf:]
1765 snip = 1
1765 snip = 1
1766 if snip and print_full == 2:
1766 if snip and print_full == 2:
1767 if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
1767 if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
1768 page(str)
1768 page(str)
1769 return snip
1769 return snip
1770
1770
1771 #****************************************************************************
1771 #****************************************************************************
1772 # lists, dicts and structures
1772 # lists, dicts and structures
1773
1773
1774 def belong(candidates,checklist):
1774 def belong(candidates,checklist):
1775 """Check whether a list of items appear in a given list of options.
1775 """Check whether a list of items appear in a given list of options.
1776
1776
1777 Returns a list of 1 and 0, one for each candidate given."""
1777 Returns a list of 1 and 0, one for each candidate given."""
1778
1778
1779 return [x in checklist for x in candidates]
1779 return [x in checklist for x in candidates]
1780
1780
1781 #----------------------------------------------------------------------------
1781 #----------------------------------------------------------------------------
1782 def uniq_stable(elems):
1782 def uniq_stable(elems):
1783 """uniq_stable(elems) -> list
1783 """uniq_stable(elems) -> list
1784
1784
1785 Return from an iterable, a list of all the unique elements in the input,
1785 Return from an iterable, a list of all the unique elements in the input,
1786 but maintaining the order in which they first appear.
1786 but maintaining the order in which they first appear.
1787
1787
1788 A naive solution to this problem which just makes a dictionary with the
1788 A naive solution to this problem which just makes a dictionary with the
1789 elements as keys fails to respect the stability condition, since
1789 elements as keys fails to respect the stability condition, since
1790 dictionaries are unsorted by nature.
1790 dictionaries are unsorted by nature.
1791
1791
1792 Note: All elements in the input must be valid dictionary keys for this
1792 Note: All elements in the input must be valid dictionary keys for this
1793 routine to work, as it internally uses a dictionary for efficiency
1793 routine to work, as it internally uses a dictionary for efficiency
1794 reasons."""
1794 reasons."""
1795
1795
1796 unique = []
1796 unique = []
1797 unique_dict = {}
1797 unique_dict = {}
1798 for nn in elems:
1798 for nn in elems:
1799 if nn not in unique_dict:
1799 if nn not in unique_dict:
1800 unique.append(nn)
1800 unique.append(nn)
1801 unique_dict[nn] = None
1801 unique_dict[nn] = None
1802 return unique
1802 return unique
1803
1803
1804 #----------------------------------------------------------------------------
1804 #----------------------------------------------------------------------------
1805 class NLprinter:
1805 class NLprinter:
1806 """Print an arbitrarily nested list, indicating index numbers.
1806 """Print an arbitrarily nested list, indicating index numbers.
1807
1807
1808 An instance of this class called nlprint is available and callable as a
1808 An instance of this class called nlprint is available and callable as a
1809 function.
1809 function.
1810
1810
1811 nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent'
1811 nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent'
1812 and using 'sep' to separate the index from the value. """
1812 and using 'sep' to separate the index from the value. """
1813
1813
1814 def __init__(self):
1814 def __init__(self):
1815 self.depth = 0
1815 self.depth = 0
1816
1816
1817 def __call__(self,lst,pos='',**kw):
1817 def __call__(self,lst,pos='',**kw):
1818 """Prints the nested list numbering levels."""
1818 """Prints the nested list numbering levels."""
1819 kw.setdefault('indent',' ')
1819 kw.setdefault('indent',' ')
1820 kw.setdefault('sep',': ')
1820 kw.setdefault('sep',': ')
1821 kw.setdefault('start',0)
1821 kw.setdefault('start',0)
1822 kw.setdefault('stop',len(lst))
1822 kw.setdefault('stop',len(lst))
1823 # we need to remove start and stop from kw so they don't propagate
1823 # we need to remove start and stop from kw so they don't propagate
1824 # into a recursive call for a nested list.
1824 # into a recursive call for a nested list.
1825 start = kw['start']; del kw['start']
1825 start = kw['start']; del kw['start']
1826 stop = kw['stop']; del kw['stop']
1826 stop = kw['stop']; del kw['stop']
1827 if self.depth == 0 and 'header' in kw.keys():
1827 if self.depth == 0 and 'header' in kw.keys():
1828 print kw['header']
1828 print kw['header']
1829
1829
1830 for idx in range(start,stop):
1830 for idx in range(start,stop):
1831 elem = lst[idx]
1831 elem = lst[idx]
1832 if type(elem)==type([]):
1832 if type(elem)==type([]):
1833 self.depth += 1
1833 self.depth += 1
1834 self.__call__(elem,itpl('$pos$idx,'),**kw)
1834 self.__call__(elem,itpl('$pos$idx,'),**kw)
1835 self.depth -= 1
1835 self.depth -= 1
1836 else:
1836 else:
1837 printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem')
1837 printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem')
1838
1838
1839 nlprint = NLprinter()
1839 nlprint = NLprinter()
1840 #----------------------------------------------------------------------------
1840 #----------------------------------------------------------------------------
1841 def all_belong(candidates,checklist):
1841 def all_belong(candidates,checklist):
1842 """Check whether a list of items ALL appear in a given list of options.
1842 """Check whether a list of items ALL appear in a given list of options.
1843
1843
1844 Returns a single 1 or 0 value."""
1844 Returns a single 1 or 0 value."""
1845
1845
1846 return 1-(0 in [x in checklist for x in candidates])
1846 return 1-(0 in [x in checklist for x in candidates])
1847
1847
1848 #----------------------------------------------------------------------------
1848 #----------------------------------------------------------------------------
1849 def sort_compare(lst1,lst2,inplace = 1):
1849 def sort_compare(lst1,lst2,inplace = 1):
1850 """Sort and compare two lists.
1850 """Sort and compare two lists.
1851
1851
1852 By default it does it in place, thus modifying the lists. Use inplace = 0
1852 By default it does it in place, thus modifying the lists. Use inplace = 0
1853 to avoid that (at the cost of temporary copy creation)."""
1853 to avoid that (at the cost of temporary copy creation)."""
1854 if not inplace:
1854 if not inplace:
1855 lst1 = lst1[:]
1855 lst1 = lst1[:]
1856 lst2 = lst2[:]
1856 lst2 = lst2[:]
1857 lst1.sort(); lst2.sort()
1857 lst1.sort(); lst2.sort()
1858 return lst1 == lst2
1858 return lst1 == lst2
1859
1859
1860 #----------------------------------------------------------------------------
1860 #----------------------------------------------------------------------------
1861 def list2dict(lst):
1861 def list2dict(lst):
1862 """Takes a list of (key,value) pairs and turns it into a dict."""
1862 """Takes a list of (key,value) pairs and turns it into a dict."""
1863
1863
1864 dic = {}
1864 dic = {}
1865 for k,v in lst: dic[k] = v
1865 for k,v in lst: dic[k] = v
1866 return dic
1866 return dic
1867
1867
1868 #----------------------------------------------------------------------------
1868 #----------------------------------------------------------------------------
1869 def list2dict2(lst,default=''):
1869 def list2dict2(lst,default=''):
1870 """Takes a list and turns it into a dict.
1870 """Takes a list and turns it into a dict.
1871 Much slower than list2dict, but more versatile. This version can take
1871 Much slower than list2dict, but more versatile. This version can take
1872 lists with sublists of arbitrary length (including sclars)."""
1872 lists with sublists of arbitrary length (including sclars)."""
1873
1873
1874 dic = {}
1874 dic = {}
1875 for elem in lst:
1875 for elem in lst:
1876 if type(elem) in (types.ListType,types.TupleType):
1876 if type(elem) in (types.ListType,types.TupleType):
1877 size = len(elem)
1877 size = len(elem)
1878 if size == 0:
1878 if size == 0:
1879 pass
1879 pass
1880 elif size == 1:
1880 elif size == 1:
1881 dic[elem] = default
1881 dic[elem] = default
1882 else:
1882 else:
1883 k,v = elem[0], elem[1:]
1883 k,v = elem[0], elem[1:]
1884 if len(v) == 1: v = v[0]
1884 if len(v) == 1: v = v[0]
1885 dic[k] = v
1885 dic[k] = v
1886 else:
1886 else:
1887 dic[elem] = default
1887 dic[elem] = default
1888 return dic
1888 return dic
1889
1889
1890 #----------------------------------------------------------------------------
1890 #----------------------------------------------------------------------------
1891 def flatten(seq):
1891 def flatten(seq):
1892 """Flatten a list of lists (NOT recursive, only works for 2d lists)."""
1892 """Flatten a list of lists (NOT recursive, only works for 2d lists)."""
1893
1893
1894 return [x for subseq in seq for x in subseq]
1894 return [x for subseq in seq for x in subseq]
1895
1895
1896 #----------------------------------------------------------------------------
1896 #----------------------------------------------------------------------------
1897 def get_slice(seq,start=0,stop=None,step=1):
1897 def get_slice(seq,start=0,stop=None,step=1):
1898 """Get a slice of a sequence with variable step. Specify start,stop,step."""
1898 """Get a slice of a sequence with variable step. Specify start,stop,step."""
1899 if stop == None:
1899 if stop == None:
1900 stop = len(seq)
1900 stop = len(seq)
1901 item = lambda i: seq[i]
1901 item = lambda i: seq[i]
1902 return map(item,xrange(start,stop,step))
1902 return map(item,xrange(start,stop,step))
1903
1903
1904 #----------------------------------------------------------------------------
1904 #----------------------------------------------------------------------------
1905 def chop(seq,size):
1905 def chop(seq,size):
1906 """Chop a sequence into chunks of the given size."""
1906 """Chop a sequence into chunks of the given size."""
1907 chunk = lambda i: seq[i:i+size]
1907 chunk = lambda i: seq[i:i+size]
1908 return map(chunk,xrange(0,len(seq),size))
1908 return map(chunk,xrange(0,len(seq),size))
1909
1909
1910 #----------------------------------------------------------------------------
1910 #----------------------------------------------------------------------------
1911 # with is a keyword as of python 2.5, so this function is renamed to withobj
1911 # with is a keyword as of python 2.5, so this function is renamed to withobj
1912 # from its old 'with' name.
1912 # from its old 'with' name.
1913 def with_obj(object, **args):
1913 def with_obj(object, **args):
1914 """Set multiple attributes for an object, similar to Pascal's with.
1914 """Set multiple attributes for an object, similar to Pascal's with.
1915
1915
1916 Example:
1916 Example:
1917 with_obj(jim,
1917 with_obj(jim,
1918 born = 1960,
1918 born = 1960,
1919 haircolour = 'Brown',
1919 haircolour = 'Brown',
1920 eyecolour = 'Green')
1920 eyecolour = 'Green')
1921
1921
1922 Credit: Greg Ewing, in
1922 Credit: Greg Ewing, in
1923 http://mail.python.org/pipermail/python-list/2001-May/040703.html.
1923 http://mail.python.org/pipermail/python-list/2001-May/040703.html.
1924
1924
1925 NOTE: up until IPython 0.7.2, this was called simply 'with', but 'with'
1925 NOTE: up until IPython 0.7.2, this was called simply 'with', but 'with'
1926 has become a keyword for Python 2.5, so we had to rename it."""
1926 has become a keyword for Python 2.5, so we had to rename it."""
1927
1927
1928 object.__dict__.update(args)
1928 object.__dict__.update(args)
1929
1929
1930 #----------------------------------------------------------------------------
1930 #----------------------------------------------------------------------------
1931 def setattr_list(obj,alist,nspace = None):
1931 def setattr_list(obj,alist,nspace = None):
1932 """Set a list of attributes for an object taken from a namespace.
1932 """Set a list of attributes for an object taken from a namespace.
1933
1933
1934 setattr_list(obj,alist,nspace) -> sets in obj all the attributes listed in
1934 setattr_list(obj,alist,nspace) -> sets in obj all the attributes listed in
1935 alist with their values taken from nspace, which must be a dict (something
1935 alist with their values taken from nspace, which must be a dict (something
1936 like locals() will often do) If nspace isn't given, locals() of the
1936 like locals() will often do) If nspace isn't given, locals() of the
1937 *caller* is used, so in most cases you can omit it.
1937 *caller* is used, so in most cases you can omit it.
1938
1938
1939 Note that alist can be given as a string, which will be automatically
1939 Note that alist can be given as a string, which will be automatically
1940 split into a list on whitespace. If given as a list, it must be a list of
1940 split into a list on whitespace. If given as a list, it must be a list of
1941 *strings* (the variable names themselves), not of variables."""
1941 *strings* (the variable names themselves), not of variables."""
1942
1942
1943 # this grabs the local variables from the *previous* call frame -- that is
1943 # this grabs the local variables from the *previous* call frame -- that is
1944 # the locals from the function that called setattr_list().
1944 # the locals from the function that called setattr_list().
1945 # - snipped from weave.inline()
1945 # - snipped from weave.inline()
1946 if nspace is None:
1946 if nspace is None:
1947 call_frame = sys._getframe().f_back
1947 call_frame = sys._getframe().f_back
1948 nspace = call_frame.f_locals
1948 nspace = call_frame.f_locals
1949
1949
1950 if type(alist) in StringTypes:
1950 if type(alist) in StringTypes:
1951 alist = alist.split()
1951 alist = alist.split()
1952 for attr in alist:
1952 for attr in alist:
1953 val = eval(attr,nspace)
1953 val = eval(attr,nspace)
1954 setattr(obj,attr,val)
1954 setattr(obj,attr,val)
1955
1955
1956 #----------------------------------------------------------------------------
1956 #----------------------------------------------------------------------------
1957 def getattr_list(obj,alist,*args):
1957 def getattr_list(obj,alist,*args):
1958 """getattr_list(obj,alist[, default]) -> attribute list.
1958 """getattr_list(obj,alist[, default]) -> attribute list.
1959
1959
1960 Get a list of named attributes for an object. When a default argument is
1960 Get a list of named attributes for an object. When a default argument is
1961 given, it is returned when the attribute doesn't exist; without it, an
1961 given, it is returned when the attribute doesn't exist; without it, an
1962 exception is raised in that case.
1962 exception is raised in that case.
1963
1963
1964 Note that alist can be given as a string, which will be automatically
1964 Note that alist can be given as a string, which will be automatically
1965 split into a list on whitespace. If given as a list, it must be a list of
1965 split into a list on whitespace. If given as a list, it must be a list of
1966 *strings* (the variable names themselves), not of variables."""
1966 *strings* (the variable names themselves), not of variables."""
1967
1967
1968 if type(alist) in StringTypes:
1968 if type(alist) in StringTypes:
1969 alist = alist.split()
1969 alist = alist.split()
1970 if args:
1970 if args:
1971 if len(args)==1:
1971 if len(args)==1:
1972 default = args[0]
1972 default = args[0]
1973 return map(lambda attr: getattr(obj,attr,default),alist)
1973 return map(lambda attr: getattr(obj,attr,default),alist)
1974 else:
1974 else:
1975 raise ValueError,'getattr_list() takes only one optional argument'
1975 raise ValueError,'getattr_list() takes only one optional argument'
1976 else:
1976 else:
1977 return map(lambda attr: getattr(obj,attr),alist)
1977 return map(lambda attr: getattr(obj,attr),alist)
1978
1978
1979 #----------------------------------------------------------------------------
1979 #----------------------------------------------------------------------------
1980 def map_method(method,object_list,*argseq,**kw):
1980 def map_method(method,object_list,*argseq,**kw):
1981 """map_method(method,object_list,*args,**kw) -> list
1981 """map_method(method,object_list,*args,**kw) -> list
1982
1982
1983 Return a list of the results of applying the methods to the items of the
1983 Return a list of the results of applying the methods to the items of the
1984 argument sequence(s). If more than one sequence is given, the method is
1984 argument sequence(s). If more than one sequence is given, the method is
1985 called with an argument list consisting of the corresponding item of each
1985 called with an argument list consisting of the corresponding item of each
1986 sequence. All sequences must be of the same length.
1986 sequence. All sequences must be of the same length.
1987
1987
1988 Keyword arguments are passed verbatim to all objects called.
1988 Keyword arguments are passed verbatim to all objects called.
1989
1989
1990 This is Python code, so it's not nearly as fast as the builtin map()."""
1990 This is Python code, so it's not nearly as fast as the builtin map()."""
1991
1991
1992 out_list = []
1992 out_list = []
1993 idx = 0
1993 idx = 0
1994 for object in object_list:
1994 for object in object_list:
1995 try:
1995 try:
1996 handler = getattr(object, method)
1996 handler = getattr(object, method)
1997 except AttributeError:
1997 except AttributeError:
1998 out_list.append(None)
1998 out_list.append(None)
1999 else:
1999 else:
2000 if argseq:
2000 if argseq:
2001 args = map(lambda lst:lst[idx],argseq)
2001 args = map(lambda lst:lst[idx],argseq)
2002 #print 'ob',object,'hand',handler,'ar',args # dbg
2002 #print 'ob',object,'hand',handler,'ar',args # dbg
2003 out_list.append(handler(args,**kw))
2003 out_list.append(handler(args,**kw))
2004 else:
2004 else:
2005 out_list.append(handler(**kw))
2005 out_list.append(handler(**kw))
2006 idx += 1
2006 idx += 1
2007 return out_list
2007 return out_list
2008
2008
2009 #----------------------------------------------------------------------------
2009 #----------------------------------------------------------------------------
2010 def get_class_members(cls):
2010 def get_class_members(cls):
2011 ret = dir(cls)
2011 ret = dir(cls)
2012 if hasattr(cls,'__bases__'):
2012 if hasattr(cls,'__bases__'):
2013 for base in cls.__bases__:
2013 for base in cls.__bases__:
2014 ret.extend(get_class_members(base))
2014 ret.extend(get_class_members(base))
2015 return ret
2015 return ret
2016
2016
2017 #----------------------------------------------------------------------------
2017 #----------------------------------------------------------------------------
2018 def dir2(obj):
2018 def dir2(obj):
2019 """dir2(obj) -> list of strings
2019 """dir2(obj) -> list of strings
2020
2020
2021 Extended version of the Python builtin dir(), which does a few extra
2021 Extended version of the Python builtin dir(), which does a few extra
2022 checks, and supports common objects with unusual internals that confuse
2022 checks, and supports common objects with unusual internals that confuse
2023 dir(), such as Traits and PyCrust.
2023 dir(), such as Traits and PyCrust.
2024
2024
2025 This version is guaranteed to return only a list of true strings, whereas
2025 This version is guaranteed to return only a list of true strings, whereas
2026 dir() returns anything that objects inject into themselves, even if they
2026 dir() returns anything that objects inject into themselves, even if they
2027 are later not really valid for attribute access (many extension libraries
2027 are later not really valid for attribute access (many extension libraries
2028 have such bugs).
2028 have such bugs).
2029 """
2029 """
2030
2030
2031 # Start building the attribute list via dir(), and then complete it
2031 # Start building the attribute list via dir(), and then complete it
2032 # with a few extra special-purpose calls.
2032 # with a few extra special-purpose calls.
2033 words = dir(obj)
2033 words = dir(obj)
2034
2034
2035 if hasattr(obj,'__class__'):
2035 if hasattr(obj,'__class__'):
2036 words.append('__class__')
2036 words.append('__class__')
2037 words.extend(get_class_members(obj.__class__))
2037 words.extend(get_class_members(obj.__class__))
2038 #if '__base__' in words: 1/0
2038 #if '__base__' in words: 1/0
2039
2039
2040 # Some libraries (such as traits) may introduce duplicates, we want to
2040 # Some libraries (such as traits) may introduce duplicates, we want to
2041 # track and clean this up if it happens
2041 # track and clean this up if it happens
2042 may_have_dupes = False
2042 may_have_dupes = False
2043
2043
2044 # this is the 'dir' function for objects with Enthought's traits
2044 # this is the 'dir' function for objects with Enthought's traits
2045 if hasattr(obj, 'trait_names'):
2045 if hasattr(obj, 'trait_names'):
2046 try:
2046 try:
2047 words.extend(obj.trait_names())
2047 words.extend(obj.trait_names())
2048 may_have_dupes = True
2048 may_have_dupes = True
2049 except TypeError:
2049 except TypeError:
2050 # This will happen if `obj` is a class and not an instance.
2050 # This will happen if `obj` is a class and not an instance.
2051 pass
2051 pass
2052
2052
2053 # Support for PyCrust-style _getAttributeNames magic method.
2053 # Support for PyCrust-style _getAttributeNames magic method.
2054 if hasattr(obj, '_getAttributeNames'):
2054 if hasattr(obj, '_getAttributeNames'):
2055 try:
2055 try:
2056 words.extend(obj._getAttributeNames())
2056 words.extend(obj._getAttributeNames())
2057 may_have_dupes = True
2057 may_have_dupes = True
2058 except TypeError:
2058 except TypeError:
2059 # `obj` is a class and not an instance. Ignore
2059 # `obj` is a class and not an instance. Ignore
2060 # this error.
2060 # this error.
2061 pass
2061 pass
2062
2062
2063 if may_have_dupes:
2063 if may_have_dupes:
2064 # eliminate possible duplicates, as some traits may also
2064 # eliminate possible duplicates, as some traits may also
2065 # appear as normal attributes in the dir() call.
2065 # appear as normal attributes in the dir() call.
2066 words = list(set(words))
2066 words = list(set(words))
2067 words.sort()
2067 words.sort()
2068
2068
2069 # filter out non-string attributes which may be stuffed by dir() calls
2069 # filter out non-string attributes which may be stuffed by dir() calls
2070 # and poor coding in third-party modules
2070 # and poor coding in third-party modules
2071 return [w for w in words if isinstance(w, basestring)]
2071 return [w for w in words if isinstance(w, basestring)]
2072
2072
2073 #----------------------------------------------------------------------------
2073 #----------------------------------------------------------------------------
2074 def import_fail_info(mod_name,fns=None):
2074 def import_fail_info(mod_name,fns=None):
2075 """Inform load failure for a module."""
2075 """Inform load failure for a module."""
2076
2076
2077 if fns == None:
2077 if fns == None:
2078 warn("Loading of %s failed.\n" % (mod_name,))
2078 warn("Loading of %s failed.\n" % (mod_name,))
2079 else:
2079 else:
2080 warn("Loading of %s from %s failed.\n" % (fns,mod_name))
2080 warn("Loading of %s from %s failed.\n" % (fns,mod_name))
2081
2081
2082 #----------------------------------------------------------------------------
2082 #----------------------------------------------------------------------------
2083 # Proposed popitem() extension, written as a method
2083 # Proposed popitem() extension, written as a method
2084
2084
2085
2085
2086 class NotGiven: pass
2086 class NotGiven: pass
2087
2087
2088 def popkey(dct,key,default=NotGiven):
2088 def popkey(dct,key,default=NotGiven):
2089 """Return dct[key] and delete dct[key].
2089 """Return dct[key] and delete dct[key].
2090
2090
2091 If default is given, return it if dct[key] doesn't exist, otherwise raise
2091 If default is given, return it if dct[key] doesn't exist, otherwise raise
2092 KeyError. """
2092 KeyError. """
2093
2093
2094 try:
2094 try:
2095 val = dct[key]
2095 val = dct[key]
2096 except KeyError:
2096 except KeyError:
2097 if default is NotGiven:
2097 if default is NotGiven:
2098 raise
2098 raise
2099 else:
2099 else:
2100 return default
2100 return default
2101 else:
2101 else:
2102 del dct[key]
2102 del dct[key]
2103 return val
2103 return val
2104
2104
2105 def wrap_deprecated(func, suggest = '<nothing>'):
2105 def wrap_deprecated(func, suggest = '<nothing>'):
2106 def newFunc(*args, **kwargs):
2106 def newFunc(*args, **kwargs):
2107 warnings.warn("Call to deprecated function %s, use %s instead" %
2107 warnings.warn("Call to deprecated function %s, use %s instead" %
2108 ( func.__name__, suggest),
2108 ( func.__name__, suggest),
2109 category=DeprecationWarning,
2109 category=DeprecationWarning,
2110 stacklevel = 2)
2110 stacklevel = 2)
2111 return func(*args, **kwargs)
2111 return func(*args, **kwargs)
2112 return newFunc
2112 return newFunc
2113
2113
2114
2114
2115 def _num_cpus_unix():
2115 def _num_cpus_unix():
2116 """Return the number of active CPUs on a Unix system."""
2116 """Return the number of active CPUs on a Unix system."""
2117 return os.sysconf("SC_NPROCESSORS_ONLN")
2117 return os.sysconf("SC_NPROCESSORS_ONLN")
2118
2118
2119
2119
2120 def _num_cpus_darwin():
2120 def _num_cpus_darwin():
2121 """Return the number of active CPUs on a Darwin system."""
2121 """Return the number of active CPUs on a Darwin system."""
2122 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
2122 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
2123 return p.stdout.read()
2123 return p.stdout.read()
2124
2124
2125
2125
2126 def _num_cpus_windows():
2126 def _num_cpus_windows():
2127 """Return the number of active CPUs on a Windows system."""
2127 """Return the number of active CPUs on a Windows system."""
2128 return os.environ.get("NUMBER_OF_PROCESSORS")
2128 return os.environ.get("NUMBER_OF_PROCESSORS")
2129
2129
2130
2130
2131 def num_cpus():
2131 def num_cpus():
2132 """Return the effective number of CPUs in the system as an integer.
2132 """Return the effective number of CPUs in the system as an integer.
2133
2133
2134 This cross-platform function makes an attempt at finding the total number of
2134 This cross-platform function makes an attempt at finding the total number of
2135 available CPUs in the system, as returned by various underlying system and
2135 available CPUs in the system, as returned by various underlying system and
2136 python calls.
2136 python calls.
2137
2137
2138 If it can't find a sensible answer, it returns 1 (though an error *may* make
2138 If it can't find a sensible answer, it returns 1 (though an error *may* make
2139 it return a large positive number that's actually incorrect).
2139 it return a large positive number that's actually incorrect).
2140 """
2140 """
2141
2141
2142 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
2142 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
2143 # for the names of the keys we needed to look up for this function. This
2143 # for the names of the keys we needed to look up for this function. This
2144 # code was inspired by their equivalent function.
2144 # code was inspired by their equivalent function.
2145
2145
2146 ncpufuncs = {'Linux':_num_cpus_unix,
2146 ncpufuncs = {'Linux':_num_cpus_unix,
2147 'Darwin':_num_cpus_darwin,
2147 'Darwin':_num_cpus_darwin,
2148 'Windows':_num_cpus_windows,
2148 'Windows':_num_cpus_windows,
2149 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
2149 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
2150 # See http://bugs.python.org/issue1082 for details.
2150 # See http://bugs.python.org/issue1082 for details.
2151 'Microsoft':_num_cpus_windows,
2151 'Microsoft':_num_cpus_windows,
2152 }
2152 }
2153
2153
2154 ncpufunc = ncpufuncs.get(platform.system(),
2154 ncpufunc = ncpufuncs.get(platform.system(),
2155 # default to unix version (Solaris, AIX, etc)
2155 # default to unix version (Solaris, AIX, etc)
2156 _num_cpus_unix)
2156 _num_cpus_unix)
2157
2157
2158 try:
2158 try:
2159 ncpus = max(1,int(ncpufunc()))
2159 ncpus = max(1,int(ncpufunc()))
2160 except:
2160 except:
2161 ncpus = 1
2161 ncpus = 1
2162 return ncpus
2162 return ncpus
2163
2163
2164 def extract_vars(*names,**kw):
2164 def extract_vars(*names,**kw):
2165 """Extract a set of variables by name from another frame.
2165 """Extract a set of variables by name from another frame.
2166
2166
2167 :Parameters:
2167 :Parameters:
2168 - `*names`: strings
2168 - `*names`: strings
2169 One or more variable names which will be extracted from the caller's
2169 One or more variable names which will be extracted from the caller's
2170 frame.
2170 frame.
2171
2171
2172 :Keywords:
2172 :Keywords:
2173 - `depth`: integer (0)
2173 - `depth`: integer (0)
2174 How many frames in the stack to walk when looking for your variables.
2174 How many frames in the stack to walk when looking for your variables.
2175
2175
2176
2176
2177 Examples:
2177 Examples:
2178
2178
2179 In [2]: def func(x):
2179 In [2]: def func(x):
2180 ...: y = 1
2180 ...: y = 1
2181 ...: print extractVars('x','y')
2181 ...: print extract_vars('x','y')
2182 ...:
2182 ...:
2183
2183
2184 In [3]: func('hello')
2184 In [3]: func('hello')
2185 {'y': 1, 'x': 'hello'}
2185 {'y': 1, 'x': 'hello'}
2186 """
2186 """
2187
2187
2188 depth = kw.get('depth',0)
2188 depth = kw.get('depth',0)
2189
2189
2190 callerNS = sys._getframe(depth+1).f_locals
2190 callerNS = sys._getframe(depth+1).f_locals
2191 return dict((k,callerNS[k]) for k in names)
2191 return dict((k,callerNS[k]) for k in names)
2192
2192
2193
2193
2194 def extract_vars_above(*names):
2194 def extract_vars_above(*names):
2195 """Extract a set of variables by name from another frame.
2195 """Extract a set of variables by name from another frame.
2196
2196
2197 Similar to extractVars(), but with a specified depth of 1, so that names
2197 Similar to extractVars(), but with a specified depth of 1, so that names
2198 are exctracted exactly from above the caller.
2198 are exctracted exactly from above the caller.
2199
2199
2200 This is simply a convenience function so that the very common case (for us)
2200 This is simply a convenience function so that the very common case (for us)
2201 of skipping exactly 1 frame doesn't have to construct a special dict for
2201 of skipping exactly 1 frame doesn't have to construct a special dict for
2202 keyword passing."""
2202 keyword passing."""
2203
2203
2204 callerNS = sys._getframe(2).f_locals
2204 callerNS = sys._getframe(2).f_locals
2205 return dict((k,callerNS[k]) for k in names)
2205 return dict((k,callerNS[k]) for k in names)
2206
2206
2207 def shexp(s):
2207 def shexp(s):
2208 """Expand $VARS and ~names in a string, like a shell
2208 """Expand $VARS and ~names in a string, like a shell
2209
2209
2210 :Examples:
2210 :Examples:
2211
2211
2212 In [2]: os.environ['FOO']='test'
2212 In [2]: os.environ['FOO']='test'
2213
2213
2214 In [3]: shexp('variable FOO is $FOO')
2214 In [3]: shexp('variable FOO is $FOO')
2215 Out[3]: 'variable FOO is test'
2215 Out[3]: 'variable FOO is test'
2216 """
2216 """
2217 return os.path.expandvars(os.path.expanduser(s))
2217 return os.path.expandvars(os.path.expanduser(s))
2218
2218
2219
2219
2220 def list_strings(arg):
2220 def list_strings(arg):
2221 """Always return a list of strings, given a string or list of strings
2221 """Always return a list of strings, given a string or list of strings
2222 as input.
2222 as input.
2223
2223
2224 :Examples:
2224 :Examples:
2225
2225
2226 In [7]: list_strings('A single string')
2226 In [7]: list_strings('A single string')
2227 Out[7]: ['A single string']
2227 Out[7]: ['A single string']
2228
2228
2229 In [8]: list_strings(['A single string in a list'])
2229 In [8]: list_strings(['A single string in a list'])
2230 Out[8]: ['A single string in a list']
2230 Out[8]: ['A single string in a list']
2231
2231
2232 In [9]: list_strings(['A','list','of','strings'])
2232 In [9]: list_strings(['A','list','of','strings'])
2233 Out[9]: ['A', 'list', 'of', 'strings']
2233 Out[9]: ['A', 'list', 'of', 'strings']
2234 """
2234 """
2235
2235
2236 if isinstance(arg,basestring): return [arg]
2236 if isinstance(arg,basestring): return [arg]
2237 else: return arg
2237 else: return arg
2238
2238
2239 def marquee(txt='',width=78,mark='*'):
2239 def marquee(txt='',width=78,mark='*'):
2240 """Return the input string centered in a 'marquee'.
2240 """Return the input string centered in a 'marquee'.
2241
2241
2242 :Examples:
2242 :Examples:
2243
2243
2244 In [16]: marquee('A test',40)
2244 In [16]: marquee('A test',40)
2245 Out[16]: '**************** A test ****************'
2245 Out[16]: '**************** A test ****************'
2246
2246
2247 In [17]: marquee('A test',40,'-')
2247 In [17]: marquee('A test',40,'-')
2248 Out[17]: '---------------- A test ----------------'
2248 Out[17]: '---------------- A test ----------------'
2249
2249
2250 In [18]: marquee('A test',40,' ')
2250 In [18]: marquee('A test',40,' ')
2251 Out[18]: ' A test '
2251 Out[18]: ' A test '
2252
2252
2253 """
2253 """
2254 if not txt:
2254 if not txt:
2255 return (mark*width)[:width]
2255 return (mark*width)[:width]
2256 nmark = (width-len(txt)-2)/len(mark)/2
2256 nmark = (width-len(txt)-2)/len(mark)/2
2257 if nmark < 0: nmark =0
2257 if nmark < 0: nmark =0
2258 marks = mark*nmark
2258 marks = mark*nmark
2259 return '%s %s %s' % (marks,txt,marks)
2259 return '%s %s %s' % (marks,txt,marks)
2260
2260
2261 #*************************** end of file <genutils.py> **********************
2261 #*************************** end of file <genutils.py> **********************
1 NO CONTENT: file renamed from setup_bdist_egg.py to tools/setup_bdist_egg.py
NO CONTENT: file renamed from setup_bdist_egg.py to tools/setup_bdist_egg.py
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now