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