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