diff --git a/IPython/Extensions/ibrowse.py b/IPython/Extensions/ibrowse.py index 0026939..ec4adcf 100644 --- a/IPython/Extensions/ibrowse.py +++ b/IPython/Extensions/ibrowse.py @@ -12,6 +12,12 @@ except NameError: import sets set = sets.Set +# Python 2.3 compatibility +try: + sorted +except NameError: + from ipipe import sorted + class UnassignedKeyError(Exception): """ @@ -21,7 +27,7 @@ class UnassignedKeyError(Exception): class UnknownCommandError(Exception): """ - Exception that is used for reporting unknown command (this should never + Exception that is used for reporting unknown commands (this should never happen). """ @@ -733,14 +739,13 @@ class ibrowse(ipipe.Display): keymap.register("pickallattrs", "C") keymap.register("pickmarked", "m") keymap.register("pickmarkedattr", "M") - keymap.register("enterdefault", "\r\n") - # FIXME: What's happening here? - keymap.register("leave", curses.KEY_BACKSPACE, "x\x08\x7f") keymap.register("hideattr", "h") keymap.register("unhideattrs", "H") keymap.register("help", "?") - keymap.register("enter", "e") + keymap.register("enter", "eenterdefault", "\r\n") keymap.register("enterattr", "E") + # FIXME: What's happening here? + keymap.register("leave", curses.KEY_BACKSPACE, "x\x08\x7f") keymap.register("detail", "d") keymap.register("detailattr", "D") keymap.register("tooglemark", " ") @@ -879,13 +884,13 @@ class ibrowse(ipipe.Display): """ self._report = msg - def enter(self, item, mode, *attrs): + def enter(self, item, *attrs): """ - Enter the object ``item`` in the mode ``mode``. If ``attrs`` is - specified, it will be used as a fixed list of attributes to display. + Enter the object ``item``. If ``attrs`` is specified, it will be used + as a fixed list of attributes to display. """ try: - iterator = ipipe.xiter(item, mode) + iterator = ipipe.xiter(item) except (KeyboardInterrupt, SystemExit): raise except Exception, exc: @@ -1135,11 +1140,10 @@ class ibrowse(ipipe.Display): cache.marked = True level.marked += 1 - def cmd_enterdefault(self): + def cmd_enter(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 - browser 'level'. + itself (i.e. how it implements iteration). This opens a new browser 'level'. """ level = self.levels[-1] try: @@ -1148,8 +1152,8 @@ class ibrowse(ipipe.Display): self.report(CommandError("No object")) curses.beep() else: - self.report("entering object (default mode)...") - self.enter(item, "default") + self.report("entering object...") + self.enter(item) def cmd_leave(self): """ @@ -1163,22 +1167,6 @@ class ibrowse(ipipe.Display): self.report(CommandError("This is the last level")) curses.beep() - def cmd_enter(self): - """ - Enter the object under the cursor. If the object provides different - enter modes a menu of all modes will be presented; choose one and enter - it (via the 'enter' or 'enterdefault' command). - """ - level = self.levels[-1] - try: - item = level.items[level.cury].item - except IndexError: - self.report(CommandError("No object")) - curses.beep() - else: - self.report("entering object...") - self.enter(item, None) - def cmd_enterattr(self): """ Enter the attribute under the cursor. @@ -1201,7 +1189,7 @@ class ibrowse(ipipe.Display): self.report(AttributeError(name)) else: self.report("entering object attribute %s..." % name) - self.enter(value, None) + self.enter(value) def cmd_detail(self): """ @@ -1219,7 +1207,7 @@ class ibrowse(ipipe.Display): else: self.report("entering detail view for object...") attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")] - self.enter(attrs, "detail") + self.enter(attrs) def cmd_detailattr(self): """ @@ -1246,7 +1234,7 @@ class ibrowse(ipipe.Display): else: 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") + self.enter(attrs) def cmd_tooglemark(self): """ @@ -1362,7 +1350,7 @@ class ibrowse(ipipe.Display): self.report(CommandError("help already active")) return - self.enter(_BrowserHelp(self), "default") + self.enter(_BrowserHelp(self)) def cmd_quit(self): """ @@ -1400,7 +1388,7 @@ class ibrowse(ipipe.Display): self.levels = [] # enter the first level - self.enter(self.input, ipipe.xiter(self.input, "default"), *self.attrs) + self.enter(self.input, *self.attrs) self._calcheaderlines(None) @@ -1551,7 +1539,7 @@ class ibrowse(ipipe.Display): except (SystemExit, KeyboardInterrupt): raise except Exception, exc: - attr = exc + value = exc if value is not ipipe.noitem: attrstyle = ipipe.xrepr(value, "footer") for (nostyle, text) in attrstyle: diff --git a/IPython/Extensions/ipipe.py b/IPython/Extensions/ipipe.py index e659d18..4164ed6 100644 --- a/IPython/Extensions/ipipe.py +++ b/IPython/Extensions/ipipe.py @@ -15,9 +15,9 @@ This gives a listing of all environment variables sorted by name. There are three types of objects in a pipeline expression: -* ``Table``s: These objects produce items. Examples are ``ls`` (listing the +* ``Table``s: These objects produce items. Examples are ``ils`` (listing the current directory, ``ienv`` (listing environment variables), ``ipwd`` (listing - user account) and ``igrp`` (listing user groups). A ``Table`` must be the + user accounts) and ``igrp`` (listing user groups). A ``Table`` must be the first object in a pipe expression. * ``Pipe``s: These objects sit in the middle of a pipe expression. They @@ -29,7 +29,7 @@ There are three types of objects in a pipeline expression: * ``Display``s: These objects can be put as the last object in a pipeline expression. There are responsible for displaying the result of the pipeline expression. If a pipeline expression doesn't end in a display object a default - display objects will be used. One example is ``browse`` which is a ``curses`` + display objects will be used. One example is ``ibrowse`` which is a ``curses`` based browser. @@ -37,47 +37,16 @@ Adding support for pipeline expressions to your own objects can be done through three extensions points (all of them optional): * An object that will be displayed as a row by a ``Display`` object should - implement the method ``__xattrs__(self, mode)``. This method must return a - sequence of attribute names. This sequence may also contain integers, which - will be treated as sequence indizes. Also supported is ``None``, which uses - the object itself and callables which will be called with the object as the - an argument. If ``__xattrs__()`` isn't implemented ``(None,)`` will be used as - the attribute sequence (i.e. the object itself (it's ``repr()`` format) will - be being displayed. The global function ``xattrs()`` implements this - functionality. - -* When an object ``foo`` is displayed in the header, footer or table cell of the - browser ``foo.__xrepr__(mode)`` is called. Mode can be ``"header"`` or - ``"footer"`` for the header or footer line and ``"cell"`` for a table cell. - ``__xrepr__()```must return an iterable (e.g. by being a generator) which - produces the following items: The first item should be a tuple containing - the alignment (-1 left aligned, 0 centered and 1 right aligned) and whether - the complete output must be displayed or if the browser is allowed to stop - output after enough text has been produced (e.g. a syntax highlighted text - line would use ``True``, but for a large data structure (i.e. a nested list, - tuple or dictionary) ``False`` would be used). The other output ``__xrepr__()`` - may produce is tuples of ``Style```objects and text (which contain the text - representation of the object; see the ``astyle`` module). If ``__xrepr__()`` - recursively outputs a data structure the function ``xrepr(object, mode)`` can - be used and ``"default"`` must be passed as the mode in these calls. This in - turn calls the ``__xrepr__()`` method on ``object`` (or uses ``repr(object)`` - as the string representation if ``__xrepr__()`` doesn't exist). - -* Objects that can be iterated by ``Pipe``s must implement the method -``__xiter__(self, mode)``. ``mode`` can take the following values: - - - ``"default"``: This is the default value and ist always used by pipeline - expressions. Other values are only used in the browser. - - ``None``: This value is passed by the browser. The object must return an - iterable of ``XMode`` objects describing all modes supported by the object. - (This should never include ``"default"`` or ``None``). - - Any other value that the object supports. - - The global function ``xiter()`` can be called to get such an iterator. If - the method ``_xiter__`` isn't implemented, ``xiter()`` falls back to - ``__iter__``. In addition to that, dictionaries and modules receive special - treatment (returning an iterator over ``(key, value)`` pairs). This makes it - possible to use dictionaries and modules in pipeline expressions, for example: + implement the method ``__xattrs__(self, mode)`` method or register an + implementation of the generic function ``xattrs``. For more info see ``xattrs``. + +* When an object ``foo`` is displayed by a ``Display`` object, the generic + function ``xrepr`` is used. + +* Objects that can be iterated by ``Pipe``s must iterable. For special cases, + where iteration for display is different than the normal iteration a special + implementation can be registered with the generic function ``xiter``. This makes + it possible to use dictionaries and modules in pipeline expressions, for example: >>> import sys >>> sys | ifilter("isinstance(value, int)") | idump @@ -152,6 +121,8 @@ try: except ImportError: grp = None +from IPython.external import simplegeneric + import path try: from IPython import genutils, ipapi @@ -229,7 +200,7 @@ def item(iterator, index, default=noitem): """ Return the ``index``th item from the iterator ``iterator``. ``index`` must be an integer (negative integers are relative to the - end (i.e. the last item produced by the iterator)). + end (i.e. the last items produced by the iterator)). If ``default`` is given, this will be the default value when the iterator doesn't contain an item at this position. Otherwise an @@ -260,6 +231,11 @@ def item(iterator, index, default=noitem): def getglobals(g): + """ + Return the global namespace that is used for expression strings in + ``ifilter`` and others. This is ``g`` or (if ``g`` is ``None``) IPython's + user namespace. + """ if g is None: if ipapi is not None: api = ipapi.get() @@ -270,6 +246,9 @@ def getglobals(g): class Descriptor(object): + """ + A ``Descriptor`` object is used for describing the attributes of objects. + """ def __hash__(self): return hash(self.__class__) ^ hash(self.key()) @@ -283,34 +262,57 @@ class Descriptor(object): pass def name(self): + """ + Return the name of this attribute for display by a ``Display`` object + (e.g. as a column title). + """ key = self.key() if key is None: return "_" return str(key) def attrtype(self, obj): - pass + """ + Return the type of this attribute (i.e. something like "attribute" or + "method"). + """ def valuetype(self, obj): - pass + """ + Return the type of this attribute value of the object ``obj``. + """ def value(self, obj): - pass + """ + Return the value of this attribute of the object ``obj``. + """ def doc(self, obj): - pass + """ + Return the documentation for this attribute. + """ def shortdoc(self, obj): + """ + Return a short documentation for this attribute (defaulting to the + first line). + """ doc = self.doc(obj) if doc is not None: doc = doc.strip().splitlines()[0].strip() return doc def iter(self, obj): + """ + Return an iterator for this attribute of the object ``obj``. + """ return xiter(self.value(obj)) class SelfDescriptor(Descriptor): + """ + A ``SelfDescriptor`` describes the object itself. + """ def key(self): return None @@ -330,6 +332,9 @@ selfdescriptor = SelfDescriptor() # there's no need for more than one class AttributeDescriptor(Descriptor): + """ + An ``AttributeDescriptor`` describes a simple attribute of an object. + """ __slots__ = ("_name", "_doc") def __init__(self, name, doc=None): @@ -359,6 +364,10 @@ class AttributeDescriptor(Descriptor): class IndexDescriptor(Descriptor): + """ + An ``IndexDescriptor`` describes an "attribute" of an object that is fetched + via ``__getitem__``. + """ __slots__ = ("_index",) def __init__(self, index): @@ -381,6 +390,10 @@ class IndexDescriptor(Descriptor): class MethodDescriptor(Descriptor): + """ + A ``MethodDescriptor`` describes a method of an object that can be called + without argument. Note that this method shouldn't change the object. + """ __slots__ = ("_name", "_doc") def __init__(self, name, doc=None): @@ -412,6 +425,11 @@ class MethodDescriptor(Descriptor): class IterAttributeDescriptor(Descriptor): + """ + An ``IterAttributeDescriptor`` works like an ``AttributeDescriptor`` but + doesn't return an attribute values (because this value might be e.g. a large + list). + """ __slots__ = ("_name", "_doc") def __init__(self, name, doc=None): @@ -444,6 +462,10 @@ class IterAttributeDescriptor(Descriptor): class IterMethodDescriptor(Descriptor): + """ + An ``IterMethodDescriptor`` works like an ``MethodDescriptor`` but doesn't + return an attribute values (because this value might be e.g. a large list). + """ __slots__ = ("_name", "_doc") def __init__(self, name, doc=None): @@ -478,6 +500,10 @@ class IterMethodDescriptor(Descriptor): class FunctionDescriptor(Descriptor): + """ + A ``FunctionDescriptor`` turns a function into a descriptor. The function + will be called with the object to get the type and value of the attribute. + """ __slots__ = ("_function", "_name", "_doc") def __init__(self, function, name=None, doc=None): @@ -572,9 +598,6 @@ class Table(object): other = other() return ichain(other, self) - def __iter__(self): - return xiter(self, "default") - class Pipe(Table): """ @@ -596,10 +619,41 @@ class Pipe(Table): def xrepr(item, mode="default"): + """ + Generic function that adds color output and different display modes to ``repr``. + + The result of an ``xrepr`` call is iterable and consists of ``(style, string)`` + tuples. The ``style`` in this tuple must be a ``Style`` object from the + ``astring`` module. To reconfigure the output the first yielded tuple can be + a ``(aligment, full)`` tuple instead of a ``(style, string)`` tuple. + ``alignment`` can be -1 for left aligned, 0 for centered and 1 for right + aligned (the default is left alignment). ``full`` is a boolean that specifies + whether the complete output must be displayed or the ``Display`` object is + allowed to stop output after enough text has been produced (e.g. a syntax + highlighted text line would use ``True``, but for a large data structure + (i.e. a nested list, tuple or dictionary) ``False`` would be used). + The default is full output. + + There are four different possible values for ``mode`` depending on where + the ``Display`` object will display ``item``: + + * ``"header"``: ``item`` will be displayed in a header line (this is used by + ``ibrowse``). + * ``"footer"``: ``item`` will be displayed in a footer line (this is used by + ``ibrowse``). + * ``"cell"``: ``item`` will be displayed in a table cell/list. + * ``"default"``: default mode. If an ``xrepr`` implementation recursively + outputs objects, ``"default"`` must be passed in the recursive calls to + ``xrepr``. + + If no implementation is registered for ``item``, ``xrepr`` will try the + ``__xrepr__`` method on ``item``. If ``item`` doesn't have an ``__xrepr__`` + method it falls back to ``repr``/``__repr__`` for all modes. + """ try: func = item.__xrepr__ except AttributeError: - pass + yield (astyle.style_default, repr(item)) else: try: for x in func(mode): @@ -608,123 +662,179 @@ def xrepr(item, mode="default"): raise except Exception: yield (astyle.style_default, repr(item)) - return - if item is None: - yield (astyle.style_type_none, repr(item)) - elif isinstance(item, bool): - yield (astyle.style_type_bool, repr(item)) - elif isinstance(item, str): - if mode == "cell": - yield (astyle.style_default, repr(item.expandtabs(tab))[1:-1]) - else: - yield (astyle.style_default, repr(item)) - elif isinstance(item, unicode): - if mode == "cell": - yield (astyle.style_default, repr(item.expandtabs(tab))[2:-1]) - else: - yield (astyle.style_default, repr(item)) - elif isinstance(item, (int, long, float)): - yield (1, True) - yield (astyle.style_type_number, repr(item)) - elif isinstance(item, complex): - yield (astyle.style_type_number, repr(item)) - elif isinstance(item, datetime.datetime): - if mode == "cell": - # Don't use strftime() here, as this requires year >= 1900 - yield (astyle.style_type_datetime, - "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \ - (item.year, item.month, item.day, - item.hour, item.minute, item.second, - item.microsecond), - ) - else: - yield (astyle.style_type_datetime, repr(item)) - elif isinstance(item, datetime.date): - if mode == "cell": - yield (astyle.style_type_datetime, - "%04d-%02d-%02d" % (item.year, item.month, item.day)) - else: - yield (astyle.style_type_datetime, repr(item)) - elif isinstance(item, datetime.time): - if mode == "cell": - yield (astyle.style_type_datetime, - "%02d:%02d:%02d.%06d" % \ - (item.hour, item.minute, item.second, item.microsecond)) - else: - 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__ +xrepr = simplegeneric.generic(xrepr) + + +def xrepr_none(self, mode="default"): + yield (astyle.style_type_none, repr(self)) +xrepr.when_object(None)(xrepr_none) + + +def xrepr_bool(self, mode="default"): + yield (astyle.style_type_bool, repr(self)) +xrepr.when_type(bool)(xrepr_bool) + + +def xrepr_str(self, mode="default"): + if mode == "cell": + yield (astyle.style_default, repr(self.expandtabs(tab))[1:-1]) + else: + yield (astyle.style_default, repr(self)) +xrepr.when_type(str)(xrepr_str) + + +def xrepr_unicode(self, mode="default"): + if mode == "cell": + yield (astyle.style_default, repr(self.expandtabs(tab))[2:-1]) + else: + yield (astyle.style_default, repr(self)) +xrepr.when_type(unicode)(xrepr_unicode) + + +def xrepr_number(self, mode="default"): + yield (1, True) + yield (astyle.style_type_number, repr(self)) +xrepr.when_type(int)(xrepr_number) +xrepr.when_type(long)(xrepr_number) +xrepr.when_type(float)(xrepr_number) + + +def xrepr_complex(self, mode="default"): + yield (astyle.style_type_number, repr(self)) +xrepr.when_type(complex)(xrepr_number) + + +def xrepr_datetime(self, mode="default"): + if mode == "cell": + # Don't use strftime() here, as this requires year >= 1900 + yield (astyle.style_type_datetime, + "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \ + (self.year, self.month, self.day, + self.hour, self.minute, self.second, + self.microsecond), + ) + else: + yield (astyle.style_type_datetime, repr(self)) +xrepr.when_type(datetime.datetime)(xrepr_datetime) + + +def xrepr_date(self, mode="default"): + if mode == "cell": + yield (astyle.style_type_datetime, + "%04d-%02d-%02d" % (self.year, self.month, self.day)) + else: + yield (astyle.style_type_datetime, repr(self)) +xrepr.when_type(datetime.date)(xrepr_date) + + +def xrepr_time(self, mode="default"): + if mode == "cell": + yield (astyle.style_type_datetime, + "%02d:%02d:%02d.%06d" % \ + (self.hour, self.minute, self.second, self.microsecond)) + else: + yield (astyle.style_type_datetime, repr(self)) +xrepr.when_type(datetime.time)(xrepr_time) + + +def xrepr_timedelta(self, mode="default"): + yield (astyle.style_type_datetime, repr(self)) +xrepr.when_type(datetime.timedelta)(xrepr_timedelta) + + +def xrepr_type(self, mode="default"): + if self.__module__ == "__builtin__": + yield (astyle.style_type_type, self.__name__) + else: + yield (astyle.style_type_type, "%s.%s" % (self.__module__, self.__name__)) +xrepr.when_type(type)(xrepr_type) + + +def xrepr_exception(self, mode="default"): + if self.__class__.__module__ == "exceptions": + classname = self.__class__.__name__ + else: + classname = "%s.%s" % \ + (self.__class__.__module__, self.__class__.__name__) + if mode == "header" or mode == "footer": + yield (astyle.style_error, "%s: %s" % (classname, self)) + else: + yield (astyle.style_error, classname) +xrepr.when_type(Exception)(xrepr_exception) + + +def xrepr_listtuple(self, mode="default"): + if mode == "header" or mode == "footer": + if self.__class__.__module__ == "__builtin__": + classname = self.__class__.__name__ else: classname = "%s.%s" % \ - (item.__class__.__module__, item.__class__.__name__) - if mode == "header" or mode == "footer": - yield (astyle.style_error, "%s: %s" % (classname, item)) - else: - yield (astyle.style_error, classname) - elif isinstance(item, (list, tuple)): - if mode == "header" or mode == "footer": - if item.__class__.__module__ == "__builtin__": - classname = item.__class__.__name__ - else: - classname = "%s.%s" % \ - (item.__class__.__module__,item.__class__.__name__) - yield (astyle.style_default, - "<%s object with %d items at 0x%x>" % \ - (classname, len(item), id(item))) + (self.__class__.__module__,self.__class__.__name__) + yield (astyle.style_default, + "<%s object with %d items at 0x%x>" % \ + (classname, len(self), id(self))) + else: + yield (-1, False) + if isinstance(self, list): + yield (astyle.style_default, "[") + end = "]" else: - yield (-1, False) - if isinstance(item, list): - yield (astyle.style_default, "[") - end = "]" - else: - yield (astyle.style_default, "(") - end = ")" - for (i, subitem) in enumerate(item): - if i: - yield (astyle.style_default, ", ") - for part in xrepr(subitem, "default"): - yield part - yield (astyle.style_default, end) - elif isinstance(item, (dict, types.DictProxyType)): - if mode == "header" or mode == "footer": - if item.__class__.__module__ == "__builtin__": - classname = item.__class__.__name__ - else: - classname = "%s.%s" % \ - (item.__class__.__module__,item.__class__.__name__) - yield (astyle.style_default, - "<%s object with %d items at 0x%x>" % \ - (classname, len(item), id(item))) + yield (astyle.style_default, "(") + end = ")" + for (i, subself) in enumerate(self): + if i: + yield (astyle.style_default, ", ") + for part in xrepr(subself, "default"): + yield part + yield (astyle.style_default, end) +xrepr.when_type(list)(xrepr_listtuple) +xrepr.when_type(tuple)(xrepr_listtuple) + + +def xrepr_dict(self, mode="default"): + if mode == "header" or mode == "footer": + if self.__class__.__module__ == "__builtin__": + classname = self.__class__.__name__ else: - yield (-1, False) - if isinstance(item, dict): - yield (astyle.style_default, "{") - end = "}" - else: - yield (astyle.style_default, "dictproxy((") - end = "})" - for (i, (key, value)) in enumerate(item.iteritems()): - if i: - yield (astyle.style_default, ", ") - for part in xrepr(key, "default"): - yield part - yield (astyle.style_default, ": ") - for part in xrepr(value, "default"): - yield part - yield (astyle.style_default, end) + classname = "%s.%s" % \ + (self.__class__.__module__,self.__class__.__name__) + yield (astyle.style_default, + "<%s object with %d items at 0x%x>" % \ + (classname, len(self), id(self))) else: - yield (astyle.style_default, repr(item)) + yield (-1, False) + if isinstance(self, dict): + yield (astyle.style_default, "{") + end = "}" + else: + yield (astyle.style_default, "dictproxy((") + end = "})" + for (i, (key, value)) in enumerate(self.iteritems()): + if i: + yield (astyle.style_default, ", ") + for part in xrepr(key, "default"): + yield part + yield (astyle.style_default, ": ") + for part in xrepr(value, "default"): + yield part + yield (astyle.style_default, end) +xrepr.when_type(dict)(xrepr_dict) +xrepr.when_type(types.DictProxyType)(xrepr_dict) def upgradexattr(attr): + """ + Convert an attribute descriptor string to a real descriptor object. + + If attr already is a descriptor object return if unmodified. A + ``SelfDescriptor`` will be returned if ``attr`` is ``None``. ``"foo"`` + returns an ``AttributeDescriptor`` for the attribute named ``"foo"``. + ``"foo()"`` returns a ``MethodDescriptor`` for the method named ``"foo"``. + ``"-foo"`` will return an ``IterAttributeDescriptor`` for the attribute + named ``"foo"`` and ``"-foo()"`` will return an ``IterMethodDescriptor`` + for the method named ``"foo"``. Furthermore integer will return the appropriate + ``IndexDescriptor`` and callables will return a ``FunctionDescriptor``. + """ if attr is None: return selfdescriptor elif isinstance(attr, Descriptor): @@ -749,6 +859,28 @@ def upgradexattr(attr): def xattrs(item, mode="default"): + """ + Generic function that returns an iterable of attribute descriptors + to be used for displaying the attributes ob the object ``item`` in display + mode ``mode``. + + There are two possible modes: + + * ``"detail"``: The ``Display`` object wants to display a detailed list + of the object attributes. + * ``"default"``: The ``Display`` object wants to display the object in a + list view. + + If no implementation is registered for the object ``item`` ``xattrs`` falls + back to trying the ``__xattrs__`` method of the object. If this doesn't + exist either, ``dir(item)`` is used for ``"detail"`` mode and ``(None,)`` + for ``"default"`` mode. + + The implementation must yield attribute descriptor (see the class + ``Descriptor`` for more info). The ``__xattrs__`` method may also return + attribute descriptor string (and ``None``) which will be converted to real + descriptors by ``upgradexattr()``. + """ try: func = item.__xattrs__ except AttributeError: @@ -760,6 +892,14 @@ def xattrs(item, mode="default"): else: for attr in func(mode): yield upgradexattr(attr) +xattrs = simplegeneric.generic(xattrs) + + +def xattrs_complex(self, mode="default"): + if mode == "detail": + return (AttributeDescriptor("real"), AttributeDescriptor("imag")) + return (selfdescriptor,) +xattrs.when_type(complex)(xattrs_complex) def _isdict(item): @@ -780,7 +920,11 @@ def _isstr(item): return False # ``__iter__`` has been redefined -def xiter(item, mode="default"): +def xiter(item): + """ + Generic function that implements iteration for pipeline expression. If no + implementation is registered for ``item`` ``xiter`` falls back to ``iter``. + """ try: func = item.__xiter__ except AttributeError: @@ -799,13 +943,11 @@ def xiter(item, mode="default"): elif _isstr(item): if not item: raise ValueError("can't enter empty string") - lines = item.splitlines() - if len(lines) <= 1: - raise ValueError("can't enter one line string") - return iter(lines) + return iter(item.splitlines()) return iter(item) else: - return iter(func(mode)) # iter() just to be safe + return iter(func()) # iter() just to be safe +xiter = simplegeneric.generic(xiter) class ichain(Pipe): @@ -819,7 +961,7 @@ class ichain(Pipe): def __iter__(self): return itertools.chain(*self.iters) - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer": for (i, item) in enumerate(self.iters): if i: @@ -954,7 +1096,7 @@ class ifile(path.path): defaultattrs = (None, "type", "size", "modestr", "owner", "group", "mdate") - def __xattrs__(self, mode): + def __xattrs__(self, mode="default"): if mode == "detail": return ( "name", @@ -995,54 +1137,51 @@ class ifile(path.path): else: return self.defaultattrs - def __xrepr__(self, mode): - try: - if self.isdir(): - name = "idir" - style = astyle.style_dir - else: - name = "ifile" - style = astyle.style_file - except IOError: - name = "ifile" - style = astyle.style_default - if mode == "cell" or mode in "header" or mode == "footer": - abspath = repr(path._base(self.normpath())) - if abspath.startswith("u"): - abspath = abspath[2:-1] - else: - abspath = abspath[1:-1] - if mode == "cell": - yield (style, abspath) - else: - yield (style, "%s(%s)" % (name, abspath)) - else: - yield (style, repr(self)) - def __iter__(self): - if self.isdir(): - yield iparentdir(self / os.pardir) - for child in sorted(self.listdir()): - yield child - else: - f = self.open("rb") - for line in f: - yield line - f.close() +def xiter_ifile(self): + if self.isdir(): + yield (self / os.pardir).abspath() + for child in sorted(self.listdir()): + yield child + else: + f = self.open("rb") + for line in f: + yield line + f.close() +xiter.when_type(ifile)(xiter_ifile) -class iparentdir(ifile): - def __xrepr__(self, mode): +# We need to implement ``xrepr`` for ``ifile`` as a generic function, because +# otherwise ``xrepr_str`` would kick in. +def xrepr_ifile(self, mode="default"): + try: + if self.isdir(): + name = "idir" + style = astyle.style_dir + else: + name = "ifile" + style = astyle.style_file + except IOError: + name = "ifile" + style = astyle.style_default + if mode == "cell" or mode in "header" or mode == "footer": + abspath = repr(path._base(self.normpath())) + if abspath.startswith("u"): + abspath = abspath[2:-1] + else: + abspath = abspath[1:-1] if mode == "cell": - yield (astyle.style_dir, os.pardir) + yield (style, abspath) else: - for part in ifile.__xrepr__(self, mode): - yield part + yield (style, "%s(%s)" % (name, abspath)) + else: + yield (style, repr(self)) +xrepr.when_type(ifile)(xrepr_ifile) class ils(Table): """ - List the current (or a specific) directory. + List the current (or a specified) directory. Examples: @@ -1056,7 +1195,9 @@ class ils(Table): self.files = files def __iter__(self): - for child in ifile(self.base).listdir(): + base = ifile(self.base) + yield (base / os.pardir).abspath() + for child in base.listdir(): if self.dirs: if self.files: yield child @@ -1067,7 +1208,7 @@ class ils(Table): if not child.isdir(): yield child - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): return ifile(self.base).__xrepr__(mode) def __repr__(self): @@ -1091,7 +1232,7 @@ class iglob(Table): for name in glob.glob(self.glob): yield ifile(name) - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer" or mode == "cell": yield (astyle.style_default, "%s(%r)" % (self.__class__.__name__, self.glob)) @@ -1125,7 +1266,7 @@ class iwalk(Table): for name in sorted(filenames): yield ifile(os.path.join(dirpath, name)) - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer" or mode == "cell": yield (astyle.style_default, "%s(%r)" % (self.__class__.__name__, self.base)) @@ -1192,7 +1333,7 @@ class ipwdentry(object): return self._getentry().pw_shell shell = property(getshell, None, None, "Login shell") - def __xattrs__(self, mode): + def __xattrs__(self, mode="default"): return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell") def __repr__(self): @@ -1212,7 +1353,7 @@ class ipwd(Table): for entry in pwd.getpwall(): yield ipwdentry(entry.pw_name) - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer" or mode == "cell": yield (astyle.style_default, "%s()" % self.__class__.__name__) else: @@ -1257,10 +1398,10 @@ class igrpentry(object): return self._getentry().gr_mem mem = property(getmem, None, None, "Members") - def __xattrs__(self, mode): + def __xattrs__(self, mode="default"): return ("name", "passwd", "gid", "mem") - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer" or mode == "cell": yield (astyle.style_default, "group ") try: @@ -1290,7 +1431,7 @@ class igrp(Table): for entry in grp.getgrall(): yield igrpentry(entry.gr_name) - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer": yield (astyle.style_default, "%s()" % self.__class__.__name__) else: @@ -1303,10 +1444,10 @@ class Fields(object): for (key, value) in fields.iteritems(): setattr(self, key, value) - def __xattrs__(self, mode): + def __xattrs__(self, mode="default"): return self.__fieldnames - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): yield (-1, False) if mode == "header" or mode == "cell": yield (astyle.style_default, self.__class__.__name__) @@ -1340,7 +1481,7 @@ class FieldTable(Table, list): def add(self, **fields): self.append(Fields(self.fields, **fields)) - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): yield (-1, False) if mode == "header" or mode == "footer": yield (astyle.style_default, self.__class__.__name__) @@ -1360,10 +1501,10 @@ class FieldTable(Table, list): class List(list): - def __xattrs__(self, mode): + def __xattrs__(self, mode="default"): return xrange(len(self)) - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): yield (-1, False) if mode == "header" or mode == "cell" or mode == "footer" or mode == "default": yield (astyle.style_default, self.__class__.__name__) @@ -1392,7 +1533,7 @@ class ienv(Table): for (key, value) in os.environ.iteritems(): yield Fields(fields, key=key, value=value) - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): if mode == "header" or mode == "cell": yield (astyle.style_default, "%s()" % self.__class__.__name__) else: @@ -1419,7 +1560,7 @@ class icsv(Pipe): for line in reader: yield List(line) - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): yield (-1, False) if mode == "header" or mode == "footer": input = getattr(self, "input", None) @@ -1472,7 +1613,7 @@ class ix(Table): self._pipeout.close() self._pipeout = None - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer": yield (astyle.style_default, "%s(%r)" % (self.__class__.__name__, self.cmd)) @@ -1552,7 +1693,7 @@ class ifilter(Pipe): if not ok and exc_info is not None: raise exc_info[0], exc_info[1], exc_info[2] - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer": input = getattr(self, "input", None) if input is not None: @@ -1623,7 +1764,7 @@ class ieval(Pipe): if not ok and exc_info is not None: raise exc_info[0], exc_info[1], exc_info[2] - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer": input = getattr(self, "input", None) if input is not None: @@ -1693,7 +1834,7 @@ class isort(Pipe): for item in items: yield item - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): if mode == "header" or mode == "footer": input = getattr(self, "input", None) if input is not None: @@ -1756,10 +1897,18 @@ class iless(Display): try: pager = os.popen(self.cmd, "w") try: - for item in xiter(self.input, "default"): - attrs = tuple(_upgradexattrs(item, "default")) - attrs = ["%s=%s" % (a.name(item), a.value(item)) for a in attrs] - pager.write(" ".join(attrs)) + for item in xiter(self.input): + first = False + for attr in xattrs(item, "default"): + if first: + first = False + else: + pager.write(" ") + attr = upgradexattr(attr) + if not isinstance(attr, SelfDescriptor): + pager.write(attr.name()) + pager.write("=") + pager.write(str(attr.value(item))) pager.write("\n") finally: pager.close() @@ -1815,7 +1964,7 @@ class idump(Display): attrset = set() colwidths = {} rows = [] - for item in xiter(self.input, "default"): + for item in xiter(self.input): row = {} attrs = self.attrs if not attrs: @@ -1871,44 +2020,11 @@ class idump(Display): stream.write("\n") -class XMode(object): +class AttributeDetail(Table): """ - An ``XMode`` object describes one enter mode available for an object + ``AttributeDetail`` objects are use for displaying a detailed list of object + attributes. """ - def __init__(self, object, mode, title=None, description=None): - """ - Create a new ``XMode`` object for the object ``object``. This object - must support the enter mode ``mode`` (i.e. ``object.__xiter__(mode)`` - must return an iterable). ``title`` and ``description`` will be - displayed in the browser when selecting among the available modes. - """ - self.object = object - self.mode = mode - self.title = title - self.description = description - - def __repr__(self): - return "<%s.%s object mode=%r at 0x%x>" % \ - (self.__class__.__module__, self.__class__.__name__, - self.mode, id(self)) - - def __xrepr__(self, mode): - if mode == "header" or mode == "footer": - yield (astyle.style_default, self.title) - else: - yield (astyle.style_default, repr(self)) - - def __xattrs__(self, mode): - if mode == "detail": - return ("object", "mode") - else: - return ("object", "mode", "title", "description") - - def __xiter__(self, mode): - return xiter(self.object, self.mode) - - -class AttributeDetail(Table): def __init__(self, object, descriptor): self.object = object self.descriptor = descriptor @@ -1934,19 +2050,21 @@ class AttributeDetail(Table): def value(self): return self.descriptor.value(self.object) - def __xattrs__(self, mode): + def __xattrs__(self, mode="default"): attrs = ("name()", "attrtype()", "valuetype()", "value()", "shortdoc()") if mode == "detail": attrs += ("doc()",) return attrs - def __xrepr__(self, mode): + def __xrepr__(self, mode="default"): yield (-1, True) + valuetype = self.valuetype() + if valuetype is not noitem: + for part in xrepr(valuetype): + yield part + yield (astyle.style_default, " ") 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, " ") yield (astyle.style_default, self.name()) yield (astyle.style_default, " of ") for part in xrepr(self.object): diff --git a/IPython/external/__init__.py b/IPython/external/__init__.py new file mode 100644 index 0000000..3104c19 --- /dev/null +++ b/IPython/external/__init__.py @@ -0,0 +1,5 @@ +""" +This package contains all third-party modules bundled with IPython. +""" + +__all__ = ["simplegeneric"] diff --git a/IPython/external/simplegeneric.py b/IPython/external/simplegeneric.py new file mode 100644 index 0000000..d8c6d5b --- /dev/null +++ b/IPython/external/simplegeneric.py @@ -0,0 +1,130 @@ +__all__ = ["generic"] + +from types import ClassType, InstanceType +classtypes = type, ClassType + +# This is version 0.6 of Philip J. Eby's simplegeneric module +# (http://cheeseshop.python.org/pypi/simplegeneric) patched to work +# with Python 2.3 (which doesn't support assigning to __name__) + +def generic(func): + """Create a simple generic function""" + + _sentinel = object() + + def _by_class(*args, **kw): + cls = args[0].__class__ + for t in type(cls.__name__, (cls,object), {}).__mro__: + f = _gbt(t, _sentinel) + if f is not _sentinel: + return f(*args, **kw) + else: + return func(*args, **kw) + + _by_type = {object: func, InstanceType: _by_class} + _gbt = _by_type.get + + def when_type(t): + """Decorator to add a method that will be called for type `t`""" + if not isinstance(t, classtypes): + raise TypeError( + "%r is not a type or class" % (t,) + ) + def decorate(f): + if _by_type.setdefault(t,f) is not f: + raise TypeError( + "%r already has method for type %r" % (func, t) + ) + return f + return decorate + + + + + + + _by_object = {} + _gbo = _by_object.get + + def when_object(o): + """Decorator to add a method that will be called for object `o`""" + def decorate(f): + if _by_object.setdefault(id(o), (o,f))[1] is not f: + raise TypeError( + "%r already has method for object %r" % (func, o) + ) + return f + return decorate + + + def dispatch(*args, **kw): + f = _gbo(id(args[0]), _sentinel) + if f is _sentinel: + for t in type(args[0]).__mro__: + f = _gbt(t, _sentinel) + if f is not _sentinel: + return f(*args, **kw) + else: + return func(*args, **kw) + else: + return f[1](*args, **kw) + + try: + dispatch.__name__ = func.__name__ + except TypeError: + pass + dispatch.__dict__ = func.__dict__.copy() + dispatch.__doc__ = func.__doc__ + dispatch.__module__ = func.__module__ + + dispatch.when_type = when_type + dispatch.when_object = when_object + dispatch.default = func + dispatch.has_object = lambda o: id(o) in _by_object + dispatch.has_type = lambda t: t in _by_type + return dispatch + + + + +def test_suite(): + import doctest + return doctest.DocFileSuite( + 'README.txt', + optionflags=doctest.ELLIPSIS|doctest.REPORT_ONLY_FIRST_FAILURE, + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/ChangeLog b/doc/ChangeLog index 3a6d547..a49c9f0 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,26 @@ +2006-11-03 Walter Doerwald + + * IPython/Extensions/ipipe.py: xrepr(), xiter() and xattrs() are now + generic functions (using Philip J. Eby's simplegeneric package). + This makes it possible to customize the display of third-party classes + without having to monkeypatch them. xiter() no longer supports a mode + argument and the XMode class has been removed. The same functionality can + be implemented via IterAttributeDescriptor and IterMethodDescriptor. + One consequence of the switch to generic functions is that xrepr() and + xattrs() implementation must define the default value for the mode + argument themselves and xattrs() implementations must return real + descriptors. + + * IPython/external: This new subpackage will contain all third-party + packages that are bundled with IPython. (The first one is simplegeneric). + + * IPython/Extensions/ipipe.py (ifile/ils): Readd output of the parent + directory which as been dropped in r1703. + + * IPython/Extensions/ipipe.py (iless): Fixed. + + * IPython/Extensions/ibrowse: Fixed sorting under Python 2.3. + 2006-11-03 Fernando Perez * scripts/ipython: remove the very first entry in sys.path which @@ -50,7 +73,7 @@ 2006-10-28 Fernando Perez - * IPython/UserConfig/ipythonrc-scipy: minor clenaups to remove old + * IPython/UserConfig/ipythonrc-scipy: minor cleanups to remove old Numeric leftovers. * ipython.el (py-execute-region): apply Stefan's patch to fix