From 9c312a73863f1f47b69362a129497297505541f5 2006-07-27 17:01:32 From: walter.doerwald Date: 2006-07-27 17:01:32 Subject: [PATCH] * IPython/Extensions/ipipe.py: Rename XAttr to AttributeDetail and make it iterable (iterating over the attribute itself). Add two new magic strings for __xattrs__(): If the string starts with "-", the attribute will not be displayed in ibrowse's detail view (but it can still be iterated over). This makes it possible to add attributes that are large lists or generator methods to the detail view. Replace magic attribute names and _attrname() and _getattr() with "descriptors": For each type of magic attribute name there's a subclass of Descriptor: None -> SelfDescriptor(); "foo" -> AttributeDescriptor("foo"); "foo()" -> MethodDescriptor("foo"); "-foo" -> IterAttributeDescriptor("foo"); "-foo()" -> IterMethodDescriptor("foo"); foo() -> FunctionDescriptor(foo). Magic strings returned from __xattrs__() are still supported. * IPython/Extensions/ibrowse.py: If fetching the next row from the input fails in ibrowse.fetch(), the exception object is added as the last item and item fetching is canceled. This prevents ibrowse from aborting if e.g. a generator throws an exception midway through execution. * IPython/Extensions/ipipe.py: Turn ifile's properties mimetype and encoding into methods. --- diff --git a/IPython/Extensions/astyle.py b/IPython/Extensions/astyle.py index fa2042e..8ac0912 100644 --- a/IPython/Extensions/astyle.py +++ b/IPython/Extensions/astyle.py @@ -374,6 +374,7 @@ style_type_none = Style.fromstr("magenta:black") style_type_bool = Style.fromstr("magenta:black") style_type_number = Style.fromstr("yellow:black") style_type_datetime = Style.fromstr("magenta:black") +style_type_type = Style.fromstr("cyan:black") # Style for URLs and file/directory names style_url = Style.fromstr("green:black") diff --git a/IPython/Extensions/ibrowse.py b/IPython/Extensions/ibrowse.py index b1356ab..1128616 100644 --- a/IPython/Extensions/ibrowse.py +++ b/IPython/Extensions/ibrowse.py @@ -97,7 +97,7 @@ class _BrowserHelp(object): else: yield (astyle.style_default, repr(self)) - def __xiter__(self, mode): + def __iter__(self): # Get reverse key mapping allkeys = {} for (key, cmd) in self.browser.keymap.iteritems(): @@ -125,7 +125,7 @@ class _BrowserHelp(object): lines = textwrap.wrap(description, 60) keys = allkeys.get(name, []) - yield ipipe.Fields(fields, description=astyle.Text((self.style_header, name))) + yield ipipe.Fields(fields, key="", description=astyle.Text((self.style_header, name))) for i in xrange(max(len(keys), len(lines))): try: key = self.browser.keylabel(keys[i]) @@ -183,13 +183,13 @@ class _BrowserLevel(object): # Size of row number (changes when scrolling) self.numbersizex = 0 - # Attribute names to display (in this order) + # Attributes to display (in this order) self.displayattrs = [] - # index and name of attribute under the cursor + # index and attribute under the cursor self.displayattr = (None, ipipe.noitem) - # Maps attribute names to column widths + # Maps attributes to column widths self.colwidths = {} # Set of hidden attributes @@ -207,6 +207,13 @@ class _BrowserLevel(object): except StopIteration: self.exhausted = True break + except (KeyboardInterrupt, SystemExit): + raise + except Exception, exc: + have += 1 + self.items.append(_BrowserCachedItem(exc)) + self.exhausted = True + break else: have += 1 self.items.append(_BrowserCachedItem(item)) @@ -215,22 +222,23 @@ class _BrowserLevel(object): # Calculate which attributes are available from the objects that are # currently visible on screen (and store it in ``self.displayattrs``) - attrnames = set() + attrs = set() self.displayattrs = [] if self.attrs: # If the browser object specifies a fixed list of attributes, # simply use it (removing hidden attributes). - for attrname in self.attrs: - if attrname not in attrnames and attrname not in self.hiddenattrs: - self.displayattrs.append(attrname) - attrnames.add(attrname) + for attr in self.attrs: + attr = ipipe.upgradexattr(attr) + if attr not in attrs and attr not in self.hiddenattrs: + self.displayattrs.append(attr) + attrs.add(attr) else: endy = min(self.datastarty+self.mainsizey, len(self.items)) for i in xrange(self.datastarty, endy): - for attrname in ipipe.xattrs(self.items[i].item, "default"): - if attrname not in attrnames and attrname not in self.hiddenattrs: - self.displayattrs.append(attrname) - attrnames.add(attrname) + for attr in ipipe.xattrs(self.items[i].item, "default"): + if attr not in attrs and attr not in self.hiddenattrs: + self.displayattrs.append(attr) + attrs.add(attr) def getrow(self, i): # Return a dictinary with the attributes for the object @@ -239,9 +247,9 @@ class _BrowserLevel(object): # called before. row = {} item = self.items[i].item - for attrname in self.displayattrs: + for attr in self.displayattrs: try: - value = ipipe._getattr(item, attrname, ipipe.noitem) + value = attr.value(item) except (KeyboardInterrupt, SystemExit): raise except Exception, exc: @@ -249,7 +257,7 @@ class _BrowserLevel(object): # only store attribute if it exists (or we got an exception) if value is not ipipe.noitem: # remember alignment, length and colored text - row[attrname] = ipipe.xformat(value, "cell", self.browser.maxattrlength) + row[attr] = ipipe.xformat(value, "cell", self.browser.maxattrlength) return row def calcwidths(self): @@ -260,16 +268,16 @@ class _BrowserLevel(object): # column names to widths. self.colwidths = {} for row in self.displayrows: - for attrname in self.displayattrs: + for attr in self.displayattrs: try: - length = row[attrname][1] + length = row[attr][1] except KeyError: length = 0 # always add attribute to colwidths, even if it doesn't exist - if attrname not in self.colwidths: - self.colwidths[attrname] = len(ipipe._attrname(attrname)) - newwidth = max(self.colwidths[attrname], length) - self.colwidths[attrname] = newwidth + if attr not in self.colwidths: + self.colwidths[attr] = len(attr.name()) + newwidth = max(self.colwidths[attr], length) + self.colwidths[attr] = newwidth # How many characters do we need to paint the largest item number? self.numbersizex = len(str(self.datastarty+self.mainsizey-1)) @@ -282,11 +290,11 @@ class _BrowserLevel(object): # Find out which attribute the cursor is on and store this # information in ``self.displayattr``. pos = 0 - for (i, attrname) in enumerate(self.displayattrs): - if pos+self.colwidths[attrname] >= self.curx: - self.displayattr = (i, attrname) + for (i, attr) in enumerate(self.displayattrs): + if pos+self.colwidths[attr] >= self.curx: + self.displayattr = (i, attr) break - pos += self.colwidths[attrname]+1 + pos += self.colwidths[attr]+1 else: self.displayattr = (None, ipipe.noitem) @@ -1030,7 +1038,7 @@ class ibrowse(ipipe.Display): """ 'Pick' the object under the cursor (i.e. the row the cursor is on). This leaves the browser and returns the picked object to the caller. - (In IPython this object will be available as the '_' variable.) + (In IPython this object will be available as the ``_`` variable.) """ level = self.levels[-1] self.returnvalue = level.items[level.cury].item @@ -1042,17 +1050,17 @@ class ibrowse(ipipe.Display): cursor is on). """ level = self.levels[-1] - attrname = level.displayattr[1] - if attrname is ipipe.noitem: + attr = level.displayattr[1] + if attr is ipipe.noitem: curses.beep() - self.report(AttributeError(ipipe._attrname(attrname))) + self.report(CommandError("no column under cursor")) return - attr = ipipe._getattr(level.items[level.cury].item, attrname) - if attr is ipipe.noitem: + value = attr.value(level.items[level.cury].item) + if value is ipipe.noitem: curses.beep() - self.report(AttributeError(ipipe._attrname(attrname))) + self.report(AttributeError(attr.name())) else: - self.returnvalue = attr + self.returnvalue = value return True def cmd_pickallattrs(self): @@ -1062,16 +1070,16 @@ class ibrowse(ipipe.Display): will be returned as a list. """ level = self.levels[-1] - attrname = level.displayattr[1] - if attrname is ipipe.noitem: + attr = level.displayattr[1] + if attr is ipipe.noitem: curses.beep() - self.report(AttributeError(ipipe._attrname(attrname))) + self.report(CommandError("no column under cursor")) return result = [] for cache in level.items: - attr = ipipe._getattr(cache.item, attrname) - if attr is not ipipe.noitem: - result.append(attr) + value = attr.value(cache.item) + if value is not ipipe.noitem: + result.append(value) self.returnvalue = result return True @@ -1090,17 +1098,17 @@ class ibrowse(ipipe.Display): """ level = self.levels[-1] - attrname = level.displayattr[1] - if attrname is ipipe.noitem: + attr = level.displayattr[1] + if attr is ipipe.noitem: curses.beep() - self.report(AttributeError(ipipe._attrname(attrname))) + self.report(CommandError("no column under cursor")) return result = [] for cache in level.items: if cache.marked: - attr = ipipe._getattr(cache.item, attrname) - if attr is not ipipe.noitem: - result.append(attr) + value = attr.value(cache.item) + if value is not ipipe.noitem: + result.append(value) self.returnvalue = result return True @@ -1130,7 +1138,7 @@ class ibrowse(ipipe.Display): def cmd_enterdefault(self): """ Enter the object under the cursor. (what this mean depends on the object - itself (i.e. how it implements the '__xiter__' method). This opens a new + itself (i.e. how it implements the ``__xiter__`` method). This opens a new browser 'level'. """ level = self.levels[-1] @@ -1176,10 +1184,10 @@ class ibrowse(ipipe.Display): Enter the attribute under the cursor. """ level = self.levels[-1] - attrname = level.displayattr[1] - if attrname is ipipe.noitem: + attr = level.displayattr[1] + if attr is ipipe.noitem: curses.beep() - self.report(AttributeError(ipipe._attrname(attrname))) + self.report(CommandError("no column under cursor")) return try: item = level.items[level.cury].item @@ -1187,12 +1195,13 @@ class ibrowse(ipipe.Display): self.report(CommandError("No object")) curses.beep() else: - attr = ipipe._getattr(item, attrname) - if attr is ipipe.noitem: - self.report(AttributeError(ipipe._attrname(attrname))) + value = attr.value(item) + name = attr.name() + if value is ipipe.noitem: + self.report(AttributeError(name)) else: - self.report("entering object attribute %s..." % ipipe._attrname(attrname)) - self.enter(attr, None) + self.report("entering object attribute %s..." % name) + self.enter(value, None) def cmd_detail(self): """ @@ -1209,17 +1218,18 @@ class ibrowse(ipipe.Display): curses.beep() else: self.report("entering detail view for object...") - self.enter(item, "detail") + attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")] + self.enter(attrs, "detail") def cmd_detailattr(self): """ Show a detail view of the attribute under the cursor. """ level = self.levels[-1] - attrname = level.displayattr[1] - if attrname is ipipe.noitem: + attr = level.displayattr[1] + if attr is ipipe.noitem: curses.beep() - self.report(AttributeError(ipipe._attrname(attrname))) + self.report(CommandError("no attribute")) return try: item = level.items[level.cury].item @@ -1227,12 +1237,16 @@ class ibrowse(ipipe.Display): self.report(CommandError("No object")) curses.beep() else: - attr = ipipe._getattr(item, attrname) - if attr is ipipe.noitem: - self.report(AttributeError(ipipe._attrname(attrname))) + try: + item = attr.value(item) + except (KeyboardInterrupt, SystemExit): + raise + except Exception, exc: + self.report(exc) else: - self.report("entering detail view for attribute...") - self.enter(attr, "detail") + self.report("entering detail view for attribute %s..." % attr.name()) + attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")] + self.enter(attrs, "detail") def cmd_tooglemark(self): """ @@ -1259,15 +1273,15 @@ class ibrowse(ipipe.Display): the cursor as the sort key. """ level = self.levels[-1] - attrname = level.displayattr[1] - if attrname is ipipe.noitem: + attr = level.displayattr[1] + if attr is ipipe.noitem: curses.beep() - self.report(AttributeError(ipipe._attrname(attrname))) + self.report(CommandError("no column under cursor")) return - self.report("sort by %s (ascending)" % ipipe._attrname(attrname)) + self.report("sort by %s (ascending)" % attr.name()) def key(item): try: - return ipipe._getattr(item, attrname, None) + return attr.value(item) except (KeyboardInterrupt, SystemExit): raise except Exception: @@ -1280,15 +1294,15 @@ class ibrowse(ipipe.Display): the cursor as the sort key. """ level = self.levels[-1] - attrname = level.displayattr[1] - if attrname is ipipe.noitem: + attr = level.displayattr[1] + if attr is ipipe.noitem: curses.beep() - self.report(AttributeError(ipipe._attrname(attrname))) + self.report(CommandError("no column under cursor")) return - self.report("sort by %s (descending)" % ipipe._attrname(attrname)) + self.report("sort by %s (descending)" % attr.name()) def key(item): try: - return ipipe._getattr(item, attrname, None) + return attr.value(item) except (KeyboardInterrupt, SystemExit): raise except Exception: @@ -1427,11 +1441,11 @@ class ibrowse(ipipe.Display): scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep)) begx = level.numbersizex+3 posx = begx-level.datastartx - for attrname in level.displayattrs: - strattrname = ipipe._attrname(attrname) - cwidth = level.colwidths[attrname] - header = strattrname.ljust(cwidth) - if attrname == level.displayattr[1]: + for attr in level.displayattrs: + attrname = attr.name() + cwidth = level.colwidths[attr] + header = attrname.ljust(cwidth) + if attr is level.displayattr[1]: style = self.style_colheaderhere else: style = self.style_colheader @@ -1527,19 +1541,19 @@ class ibrowse(ipipe.Display): break attrstyle = [(astyle.style_default, "no attribute")] - attrname = level.displayattr[1] - if attrname is not ipipe.noitem and attrname is not None: + attr = level.displayattr[1] + if attr is not ipipe.noitem and not isinstance(attr, ipipe.SelfDescriptor): posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer) - posx += self.addstr(posy, posx, 0, endx, ipipe._attrname(attrname), self.style_footer) + posx += self.addstr(posy, posx, 0, endx, attr.name(), self.style_footer) posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer) try: - attr = ipipe._getattr(item, attrname) + value = attr.value(item) except (SystemExit, KeyboardInterrupt): raise except Exception, exc: attr = exc - if attr is not ipipe.noitem: - attrstyle = ipipe.xrepr(attr, "footer") + if value is not ipipe.noitem: + attrstyle = ipipe.xrepr(value, "footer") for (nostyle, text) in attrstyle: if not isinstance(nostyle, int): posx += self.addstr(posy, posx, 0, endx, text, self.style_footer) diff --git a/IPython/Extensions/ipipe.py b/IPython/Extensions/ipipe.py index 1e4b063..7aafa73 100644 --- a/IPython/Extensions/ipipe.py +++ b/IPython/Extensions/ipipe.py @@ -269,6 +269,251 @@ def getglobals(g): return g +class Descriptor(object): + def __hash__(self): + return hash(self.__class__) ^ hash(self.key()) + + def __eq__(self, other): + return self.__class__ is other.__class__ and self.key() == other.key() + + def __ne__(self, other): + return self.__class__ is not other.__class__ or self.key() != other.key() + + def key(self): + pass + + def name(self): + key = self.key() + if key is None: + return "_" + return str(key) + + def attrtype(self, obj): + pass + + def valuetype(self, obj): + pass + + def value(self, obj): + pass + + def doc(self, obj): + pass + + def shortdoc(self, obj): + doc = self.doc(obj) + if doc is not None: + doc = doc.strip().splitlines()[0].strip() + return doc + + def iter(self, obj): + return xiter(self.value(obj)) + + +class SelfDescriptor(Descriptor): + def key(self): + return None + + def attrtype(self, obj): + return "self" + + def valuetype(self, obj): + return type(obj) + + def value(self, obj): + return obj + + def __repr__(self): + return "Self" + +selfdescriptor = SelfDescriptor() # there's no need for more than one + + +class AttributeDescriptor(Descriptor): + __slots__ = ("_name", "_doc") + + def __init__(self, name, doc=None): + self._name = name + self._doc = doc + + def key(self): + return self._name + + def doc(self, obj): + return self._doc + + def attrtype(self, obj): + return "attr" + + def valuetype(self, obj): + return type(getattr(obj, self._name)) + + def value(self, obj): + return getattr(obj, self._name) + + def __repr__(self): + if self._doc is None: + return "Attribute(%r)" % self._name + else: + return "Attribute(%r, %r)" % (self._name, self._doc) + + +class IndexDescriptor(Descriptor): + __slots__ = ("_index",) + + def __init__(self, index): + self._index = index + + def key(self): + return self._index + + def attrtype(self, obj): + return "item" + + def valuetype(self, obj): + return type(obj[self._index]) + + def value(self, obj): + return obj[self._index] + + def __repr__(self): + return "Index(%r)" % self._index + + +class MethodDescriptor(Descriptor): + __slots__ = ("_name", "_doc") + + def __init__(self, name, doc=None): + self._name = name + self._doc = doc + + def key(self): + return self._name + + def doc(self, obj): + if self._doc is None: + return getattr(obj, self._name).__doc__ + return self._doc + + def attrtype(self, obj): + return "method" + + def valuetype(self, obj): + return type(self.value(obj)) + + def value(self, obj): + return getattr(obj, self._name)() + + def __repr__(self): + if self._doc is None: + return "Method(%r)" % self._name + else: + return "Method(%r, %r)" % (self._name, self._doc) + + +class IterAttributeDescriptor(Descriptor): + __slots__ = ("_name", "_doc") + + def __init__(self, name, doc=None): + self._name = name + self._doc = doc + + def key(self): + return self._name + + def doc(self, obj): + return self._doc + + def attrtype(self, obj): + return "iter" + + def valuetype(self, obj): + return noitem + + def value(self, obj): + return noitem + + def iter(self, obj): + return xiter(getattr(obj, self._name)) + + def __repr__(self): + if self._doc is None: + return "IterAttribute(%r)" % self._name + else: + return "IterAttribute(%r, %r)" % (self._name, self._doc) + + +class IterMethodDescriptor(Descriptor): + __slots__ = ("_name", "_doc") + + def __init__(self, name, doc=None): + self._name = name + self._doc = doc + + def key(self): + return self._name + + def doc(self, obj): + if self._doc is None: + return getattr(obj, self._name).__doc__ + return self._doc + + def attrtype(self, obj): + return "itermethod" + + def valuetype(self, obj): + return noitem + + def value(self, obj): + return noitem + + def iter(self, obj): + return xiter(getattr(obj, self._name)()) + + def __repr__(self): + if self._doc is None: + return "IterMethod(%r)" % self._name + else: + return "IterMethod(%r, %r)" % (self._name, self._doc) + + +class FunctionDescriptor(Descriptor): + __slots__ = ("_function", "_name", "_doc") + + def __init__(self, function, name=None, doc=None): + self._function = function + self._name = name + self._doc = doc + + def key(self): + return self._function + + def name(self): + if self._name is not None: + return self._name + return getattr(self._function, "__xname__", self._function.__name__) + + def doc(self, obj): + if self._doc is None: + return self._function.__doc__ + return self._doc + + def attrtype(self, obj): + return "function" + + def valuetype(self, obj): + return type(self._function(obj)) + + def value(self, obj): + return self._function(obj) + + def __repr__(self): + if self._doc is None: + return "Function(%r)" % self._name + else: + return "Function(%r, %r)" % (self._name, self._doc) + + class Table(object): """ A ``Table`` is an object that produces items (just like a normal Python @@ -350,47 +595,7 @@ class Pipe(Table): return self -def _getattr(obj, name, default=noitem): - """ - Internal helper for getting an attribute of an item. If ``name`` is ``None`` - return the object itself. If ``name`` is an integer, use ``__getitem__`` - instead. If the attribute or item does not exist, return ``default``. - """ - if name is None: - return obj - elif isinstance(name, basestring): - if name.endswith("()"): - return getattr(obj, name[:-2], default)() - else: - return getattr(obj, name, default) - elif callable(name): - try: - return name(obj) - except AttributeError: - return default - else: - try: - return obj[name] - except IndexError: - return default - - -def _attrname(name): - """ - Internal helper that gives a proper name for the attribute ``name`` - (which might be ``None`` or an ``int``). - """ - if name is None: - return "_" - elif isinstance(name, basestring): - return name - elif callable(name): - return getattr(name, "__xname__", name.__name__) - else: - return str(name) - - -def xrepr(item, mode): +def xrepr(item, mode="default"): try: func = item.__xrepr__ except AttributeError: @@ -449,6 +654,11 @@ def xrepr(item, mode): yield (astyle.style_type_datetime, repr(item)) elif isinstance(item, datetime.timedelta): yield (astyle.style_type_datetime, repr(item)) + elif isinstance(item, type): + if item.__module__ == "__builtin__": + yield (astyle.style_type_type, item.__name__) + else: + yield (astyle.style_type_type, "%s.%s" % (item.__module__, item.__name__)) elif isinstance(item, Exception): if item.__class__.__module__ == "exceptions": classname = item.__class__.__name__ @@ -514,33 +724,67 @@ def xrepr(item, mode): yield (astyle.style_default, repr(item)) -def xattrs(item, mode): +def upgradexattr(attr): + if attr is None: + return selfdescriptor + elif isinstance(attr, Descriptor): + return attr + elif isinstance(attr, str): + if attr.endswith("()"): + if attr.startswith("-"): + return IterMethodDescriptor(attr[1:-2]) + else: + return MethodDescriptor(attr[:-2]) + else: + if attr.startswith("-"): + return IterAttributeDescriptor(attr[1:]) + else: + return AttributeDescriptor(attr) + elif isinstance(attr, (int, long)): + return IndexDescriptor(attr) + elif callable(attr): + return FunctionDescriptor(attr) + else: + raise TypeError("can't handle descriptor %r" % attr) + + +def xattrs(item, mode="default"): try: func = item.__xattrs__ except AttributeError: if mode == "detail": - return dir(item) + for attrname in dir(item): + yield AttributeDescriptor(attrname) else: - return (None,) + yield selfdescriptor else: - try: - return func(mode) - except (KeyboardInterrupt, SystemExit): - raise - except Exception: - return (None,) + for attr in func(mode): + yield upgradexattr(attr) -def xiter(item, mode): - if mode == "detail": - def items(): - for name in xattrs(item, mode): - yield XAttr(item, name) - return items() +def _isdict(item): + try: + itermeth = item.__class__.__iter__ + except (AttributeError, TypeError): + return False + return itermeth is dict.__iter__ or itermeth is types.DictProxyType.__iter__ + + +def _isstr(item): + if not isinstance(item, basestring): + return False + try: + itermeth = item.__class__.__iter__ + except AttributeError: + return True + return False # ``__iter__`` has been redefined + + +def xiter(item, mode="default"): try: func = item.__xiter__ except AttributeError: - if isinstance(item, (dict, types.DictProxyType)): + if _isdict(item): def items(item): fields = ("key", "value") for (key, value) in item.iteritems(): @@ -552,8 +796,8 @@ def xiter(item, mode): for key in sorted(item.__dict__): yield Fields(fields, key=key, value=getattr(item, key)) return items(item) - elif isinstance(item, basestring): - if not len(item): + elif _isstr(item): + if not item: raise ValueError("can't enter empty string") lines = item.splitlines() if len(lines) <= 1: @@ -572,7 +816,7 @@ class ichain(Pipe): def __init__(self, *iters): self.iters = iters - def __xiter__(self, mode): + def __iter__(self): return itertools.chain(*self.iters) def __xrepr__(self, mode): @@ -823,30 +1067,61 @@ class ifile(path.path): return datetime.datetime.utcfromtimestamp(self.mtime) mdate = property(getmdate, None, None, "Modification date") - def getmimetype(self): + def mimetype(self): + """ + Return MIME type guessed from the extension. + """ return mimetypes.guess_type(self.basename())[0] - mimetype = property(getmimetype, None, None, "MIME type") - def getencoding(self): + def encoding(self): + """ + Return guessed compression (like "compress" or "gzip"). + """ return mimetypes.guess_type(self.basename())[1] - encoding = property(getencoding, None, None, "Compression") def __repr__(self): return "ifile(%s)" % path._base.__repr__(self) - defaultattrs = (None, "type", "size", "modestr", "owner", "group", "mdate") - def __xattrs__(self, mode): if mode == "detail": return ( - "name", "basename()", "abspath()", "realpath()", - "type", "mode", "modestr", "stat()", "lstat()", - "uid", "gid", "owner", "group", "dev", "nlink", - "ctime", "mtime", "atime", "cdate", "mdate", "adate", - "size", "blocks", "blksize", "isdir()", "islink()", - "mimetype", "encoding" + "name", + "basename()", + "abspath()", + "realpath()", + "type", + "mode", + "modestr", + "stat()", + "lstat()", + "uid", + "gid", + "owner", + "group", + "dev", + "nlink", + "ctime", + "mtime", + "atime", + "cdate", + "mdate", + "adate", + "size", + "blocks", + "blksize", + "isdir()", + "islink()", + "mimetype()", + "encoding()", + "-listdir()", + "-dirs()", + "-files()", + "-walk()", + "-walkdirs()", + "-walkfiles()", ) - return self.defaultattrs + else: + return (None, "type", "size", "modestr", "owner", "group", "mdate") def __xrepr__(self, mode): try: @@ -872,7 +1147,7 @@ class ifile(path.path): else: yield (style, repr(self)) - def __xiter__(self, mode): + def __iter__(self): if self.isdir(): yield iparentdir(self / os.pardir) for child in sorted(self.listdir()): @@ -906,8 +1181,8 @@ class ils(Table): def __init__(self, base=os.curdir): self.base = os.path.expanduser(base) - def __xiter__(self, mode): - return xiter(ifile(self.base), mode) + def __iter__(self): + return xiter(ifile(self.base)) def __xrepr__(self, mode): return ifile(self.base).__xrepr__(mode) @@ -929,7 +1204,7 @@ class iglob(Table): def __init__(self, glob): self.glob = glob - def __xiter__(self, mode): + def __iter__(self): for name in glob.glob(self.glob): yield ifile(name) @@ -958,7 +1233,7 @@ class iwalk(Table): self.dirs = dirs self.files = files - def __xiter__(self, mode): + def __iter__(self): for (dirpath, dirnames, filenames) in os.walk(self.base): if self.dirs: for name in sorted(dirnames): @@ -1035,7 +1310,7 @@ class ipwdentry(object): shell = property(getshell, None, None, "Login shell") def __xattrs__(self, mode): - return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell") + return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell") def __repr__(self): return "%s.%s(%r)" % \ @@ -1115,7 +1390,7 @@ class igrpentry(object): else: yield (astyle.style_default, repr(self)) - def __xiter__(self, mode): + def __iter__(self): for member in self.mem: yield ipwdentry(member) @@ -1128,7 +1403,7 @@ class igrp(Table): """ This ``Table`` lists all entries in the Unix group database. """ - def __xiter__(self, mode): + def __iter__(self): for entry in grp.getgrall(): yield igrpentry(entry.gr_name) @@ -1141,7 +1416,7 @@ class igrp(Table): class Fields(object): def __init__(self, fieldnames, **fields): - self.__fieldnames = fieldnames + self.__fieldnames = [upgradexattr(fieldname) for fieldname in fieldnames] for (key, value) in fields.iteritems(): setattr(self, key, value) @@ -1156,7 +1431,7 @@ class Fields(object): for (i, f) in enumerate(self.__fieldnames): if i: yield (astyle.style_default, ", ") - yield (astyle.style_default, f) + yield (astyle.style_default, f.name()) yield (astyle.style_default, "=") for part in xrepr(getattr(self, f), "default"): yield part @@ -1167,7 +1442,7 @@ class Fields(object): for (i, f) in enumerate(self.__fieldnames): if i: yield (astyle.style_default, ", ") - yield (astyle.style_default, f) + yield (astyle.style_default, f.name()) yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) @@ -1182,9 +1457,6 @@ class FieldTable(Table, list): def add(self, **fields): self.append(Fields(self.fields, **fields)) - def __xiter__(self, mode): - return list.__iter__(self) - def __xrepr__(self, mode): yield (-1, False) if mode == "header" or mode == "footer": @@ -1232,7 +1504,7 @@ class ienv(Table): >>> ienv """ - def __xiter__(self, mode): + def __iter__(self): fields = ("key", "value") for (key, value) in os.environ.iteritems(): yield Fields(fields, key=key, value=value) @@ -1256,7 +1528,7 @@ class icsv(Pipe): """ self.csvargs = csvargs - def __xiter__(self, mode): + def __iter__(self): input = self.input if isinstance(input, ifile): input = input.open("rb") @@ -1304,7 +1576,7 @@ class ix(Table): self.cmd = cmd self._pipeout = None - def __xiter__(self, mode="default"): + def __iter__(self): (_pipein, self._pipeout) = os.popen4(self.cmd) _pipein.close() for l in self._pipeout: @@ -1364,7 +1636,7 @@ class ifilter(Pipe): self.globals = globals self.errors = errors - def __xiter__(self, mode): + def __iter__(self): if callable(self.expr): test = self.expr else: @@ -1375,7 +1647,7 @@ class ifilter(Pipe): ok = 0 exc_info = None - for item in xiter(self.input, mode): + for item in xiter(self.input): try: if test(item): yield item @@ -1437,7 +1709,7 @@ class ieval(Pipe): self.globals = globals self.errors = errors - def __xiter__(self, mode): + def __iter__(self): if callable(self.expr): do = self.expr else: @@ -1448,7 +1720,7 @@ class ieval(Pipe): ok = 0 exc_info = None - for item in xiter(self.input, mode): + for item in xiter(self.input): try: yield do(item) except (KeyboardInterrupt, SystemExit): @@ -1497,9 +1769,9 @@ class ienum(Pipe): >>> xrange(20) | ieval("_,_*_") | ienum | ifilter("index % 2 == 0") | ieval("object") """ - def __xiter__(self, mode): + def __iter__(self): fields = ("index", "object") - for (index, object) in enumerate(xiter(self.input, mode)): + for (index, object) in enumerate(xiter(self.input)): yield Fields(fields, index=index, object=object) @@ -1524,18 +1796,11 @@ class isort(Pipe): self.globals = globals self.reverse = reverse - def __xiter__(self, mode): + def __iter__(self): if self.key is None: - items = sorted( - xiter(self.input, mode), - reverse=self.reverse - ) + items = sorted(xiter(self.input), reverse=self.reverse) elif callable(self.key): - items = sorted( - xiter(self.input, mode), - key=self.key, - reverse=self.reverse - ) + items = sorted(xiter(self.input), key=self.key, reverse=self.reverse) else: g = getglobals(self.globals) key = compile(self.key, "ipipe-expression", "eval") @@ -1613,8 +1878,8 @@ class iless(Display): pager = os.popen(self.cmd, "w") try: for item in xiter(self.input, "default"): - attrs = xattrs(item, "default") - attrs = ["%s=%s" % (a, _format(_getattr(item, a))) for a in attrs] + attrs = tuple(_upgradexattrs(item, "default")) + attrs = ["%s=%s" % (a.name(item), a.value(item)) for a in attrs] pager.write(" ".join(attrs)) pager.write("\n") finally: @@ -1659,7 +1924,7 @@ class idump(Display): style_header = astyle.Style.fromstr("white:black:bold") def __init__(self, *attrs): - self.attrs = attrs + self.attrs = [upgradexattr(attr) for attr in attrs] self.headerpadchar = " " self.headersepchar = "|" self.datapadchar = " " @@ -1668,7 +1933,7 @@ class idump(Display): def display(self): stream = genutils.Term.cout allattrs = [] - allattrset = set() + attrset = set() colwidths = {} rows = [] for item in xiter(self.input, "default"): @@ -1676,42 +1941,43 @@ class idump(Display): attrs = self.attrs if not attrs: attrs = xattrs(item, "default") - for attrname in attrs: - if attrname not in allattrset: - allattrs.append(attrname) - allattrset.add(attrname) - colwidths[attrname] = len(_attrname(attrname)) + for attr in attrs: + if attr not in attrset: + allattrs.append(attr) + attrset.add(attr) + colwidths[attr] = len(attr.name()) try: - value = _getattr(item, attrname, None) + value = attr.value(item) except (KeyboardInterrupt, SystemExit): raise except Exception, exc: value = exc (align, width, text) = xformat(value, "cell", self.maxattrlength) - colwidths[attrname] = max(colwidths[attrname], width) + colwidths[attr] = max(colwidths[attr], width) # remember alignment, length and colored parts - row[attrname] = (align, width, text) + row[attr] = (align, width, text) rows.append(row) stream.write("\n") - for (i, attrname) in enumerate(allattrs): - self.style_header(_attrname(attrname)).write(stream) - spc = colwidths[attrname] - len(_attrname(attrname)) + for (i, attr) in enumerate(allattrs): + attrname = attr.name() + self.style_header(attrname).write(stream) + spc = colwidths[attr] - len(attrname) if i < len(colwidths)-1: stream.write(self.headerpadchar*spc) stream.write(self.headersepchar) stream.write("\n") for row in rows: - for (i, attrname) in enumerate(allattrs): - (align, width, text) = row[attrname] - spc = colwidths[attrname] - width + for (i, attr) in enumerate(allattrs): + (align, width, text) = row[attr] + spc = colwidths[attr] - width if align == -1: text.write(stream) if i < len(colwidths)-1: stream.write(self.datapadchar*spc) elif align == 0: - spc = colwidths[attrname] - width + spc = colwidths[attr] - width spc1 = spc//2 spc2 = spc-spc1 stream.write(self.datapadchar*spc1) @@ -1755,55 +2021,57 @@ class XMode(object): def __xattrs__(self, mode): if mode == "detail": + return ("object", "mode") + else: return ("object", "mode", "title", "description") - return ("title", "description") def __xiter__(self, mode): return xiter(self.object, self.mode) -class XAttr(object): - def __init__(self, object, name): - self.name = _attrname(name) +class AttributeDetail(Table): + def __init__(self, object, descriptor): + self.object = object + self.descriptor = descriptor - try: - self.value = _getattr(object, name) - except (KeyboardInterrupt, SystemExit): - raise - except Exception, exc: - if exc.__class__.__module__ == "exceptions": - self.value = exc.__class__.__name__ - else: - self.value = "%s.%s" % \ - (exc.__class__.__module__, exc.__class__.__name__) - self.type = self.value - else: - t = type(self.value) - if t.__module__ == "__builtin__": - self.type = t.__name__ - else: - self.type = "%s.%s" % (t.__module__, t.__name__) + def __iter__(self): + return self.descriptor.iter(self.object) - doc = None - if isinstance(name, basestring): - if name.endswith("()"): - doc = getattr(getattr(object, name[:-2]), "__doc__", None) - else: - try: - meta = getattr(type(object), name) - except AttributeError: - pass - else: - if isinstance(meta, property): - doc = getattr(meta, "__doc__", None) - elif callable(name): - doc = getattr(name, "__doc__", None) - if isinstance(doc, basestring): - doc = doc.strip() - self.doc = doc + def name(self): + return self.descriptor.name() + + def attrtype(self): + return self.descriptor.attrtype(self.object) + + def valuetype(self): + return self.descriptor.valuetype(self.object) + + def doc(self): + return self.descriptor.doc(self.object) + + def shortdoc(self): + return self.descriptor.shortdoc(self.object) + + def value(self): + return self.descriptor.value(self.object) def __xattrs__(self, mode): - return ("name", "type", "doc", "value") + attrs = ("name()", "attrtype()", "valuetype()", "value()", "shortdoc()") + if mode == "detail": + attrs += ("doc()",) + return attrs + + def __xrepr__(self, mode): + yield (-1, True) + yield (astyle.style_default, self.attrtype()) + yield (astyle.style_default, "(") + for part in xrepr(self.valuetype()): + yield part + yield (astyle.style_default, ") ") + yield (astyle.style_default, self.name()) + yield (astyle.style_default, " of ") + for part in xrepr(self.object): + yield part try: diff --git a/doc/ChangeLog b/doc/ChangeLog index c8d02c0..d65f8ef 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -3,6 +3,27 @@ * IPython/Extensions/ipipe.py: Fix getglobals() if we're not running under IPython. + * IPython/Extensions/ipipe.py: Rename XAttr to AttributeDetail + and make it iterable (iterating over the attribute itself). Add two new + magic strings for __xattrs__(): If the string starts with "-", the attribute + will not be displayed in ibrowse's detail view (but it can still be + iterated over). This makes it possible to add attributes that are large + lists or generator methods to the detail view. Replace magic attribute names + and _attrname() and _getattr() with "descriptors": For each type of magic + attribute name there's a subclass of Descriptor: None -> SelfDescriptor(); + "foo" -> AttributeDescriptor("foo"); "foo()" -> MethodDescriptor("foo"); + "-foo" -> IterAttributeDescriptor("foo"); "-foo()" -> IterMethodDescriptor("foo"); + foo() -> FunctionDescriptor(foo). Magic strings returned from __xattrs__() + are still supported. + + * IPython/Extensions/ibrowse.py: If fetching the next row from the input + fails in ibrowse.fetch(), the exception object is added as the last item + and item fetching is canceled. This prevents ibrowse from aborting if e.g. + a generator throws an exception midway through execution. + + * IPython/Extensions/ipipe.py: Turn ifile's properties mimetype and + encoding into methods. + 2006-07-26 Ville Vainio * iplib.py: history now stores multiline input as single