# -*- coding: iso-8859-1 -*- """ ``ipipe`` provides classes to be used in an interactive Python session. Doing a ``from ipipe import *`` is the preferred way to do this. The name of all objects imported this way starts with ``i`` to minimize collisions. ``ipipe`` supports "pipeline expressions", which is something resembling Unix pipes. An example is: >>> ienv | isort("key.lower()") 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 current directory, ``ienv`` (listing environment variables), ``ipwd`` (listing user account) 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 transform the input in some way (e.g. filtering or sorting it). Examples are: ``ifilter`` (which filters the input pipe), ``isort`` (which sorts the input pipe) and ``ieval`` (which evaluates a function or expression for each object in the input pipe). * ``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`` based browser. 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: >>> import sys >>> sys | ifilter("isinstance(value, int)") | idump key |value api_version| 1012 dllhandle | 503316480 hexversion | 33817328 maxint |2147483647 maxunicode | 65535 >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()") ... Note: The expression strings passed to ``ifilter()`` and ``isort()`` can refer to the object to be filtered or sorted via the variable ``_`` and to any of the attributes of the object, i.e.: >>> sys.modules | ifilter("_.value is not None") | isort("_.key.lower()") does the same as >>> sys.modules | ifilter("value is not None") | isort("key.lower()") In addition to expression strings, it's possible to pass callables (taking the object as an argument) to ``ifilter()``, ``isort()`` and ``ieval()``: >>> sys | ifilter(lambda _:isinstance(_.value, int)) \ ... | ieval(lambda _: (_.key, hex(_.value))) | idump 0 |1 api_version|0x3f4 dllhandle |0x1e000000 hexversion |0x20402f0 maxint |0x7fffffff maxunicode |0xffff """ import sys, os, os.path, stat, glob, new, csv, datetime, types import itertools, mimetypes try: # Python 2.3 compatibility import collections except ImportError: deque = list else: deque = collections.deque try: # Python 2.3 compatibility set except NameError: import sets set = sets.Set try: # Python 2.3 compatibility sorted except NameError: def sorted(iterator, key=None, reverse=False): items = list(iterator) if key is not None: items.sort(lambda i1, i2: cmp(key(i1), key(i2))) else: items.sort() if reverse: items.reverse() return items try: import pwd except ImportError: pwd = None try: import grp except ImportError: grp = None import path try: from IPython import genutils, ipapi except ImportError: genutils = None ipapi = None import astyle __all__ = [ "ifile", "ils", "iglob", "iwalk", "ipwdentry", "ipwd", "igrpentry", "igrp", "icsv", "ix", "ichain", "isort", "ifilter", "ieval", "ienum", "ienv", "idump", "iless" ] os.stat_float_times(True) # enable microseconds class AttrNamespace(object): """ Helper class that is used for providing a namespace for evaluating expressions containing attribute names of an object. """ def __init__(self, wrapped): self.wrapped = wrapped def __getitem__(self, name): if name == "_": return self.wrapped try: return getattr(self.wrapped, name) except AttributeError: raise KeyError(name) # Python 2.3 compatibility # use eval workaround to find out which names are used in the # eval string and put them into the locals. This works for most # normal uses case, bizarre ones like accessing the locals() # will fail try: eval("_", None, AttrNamespace(None)) except TypeError: real_eval = eval def eval(codestring, _globals, _locals): """ eval(source[, globals[, locals]]) -> value Evaluate the source in the context of globals and locals. The source may be a string representing a Python expression or a code object as returned by compile(). The globals must be a dictionary and locals can be any mappping. This function is a workaround for the shortcomings of Python 2.3's eval. """ if isinstance(codestring, basestring): code = compile(codestring, "_eval", "eval") else: code = codestring newlocals = {} for name in code.co_names: try: newlocals[name] = _locals[name] except KeyError: pass return real_eval(code, _globals, newlocals) noitem = object() 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)). If ``default`` is given, this will be the default value when the iterator doesn't contain an item at this position. Otherwise an ``IndexError`` will be raised. Note that using this function will partially or totally exhaust the iterator. """ i = index if i>=0: for item in iterator: if not i: return item i -= 1 else: i = -index cache = deque() for item in iterator: cache.append(item) if len(cache)>i: cache.popleft() if len(cache)==i: return cache.popleft() if default is noitem: raise IndexError(index) else: return default def getglobals(g): if g is None: if ipapi is not None: api = ipapi.get() if api is not None: return api.user_ns return globals() 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 iterator/generator does) and can be used as the first object in a pipeline expression. The displayhook will open the default browser for such an object (instead of simply printing the ``repr()`` result). """ # We want to support ``foo`` and ``foo()`` in pipeline expression: # So we implement the required operators (``|`` and ``+``) in the metaclass, # instantiate the class and forward the operator to the instance class __metaclass__(type): def __iter__(self): return iter(self()) def __or__(self, other): return self() | other def __add__(self, other): return self() + other def __radd__(self, other): return other + self() def __getitem__(self, index): return self()[index] def __getitem__(self, index): return item(self, index) def __contains__(self, item): for haveitem in self: if item == haveitem: return True return False def __or__(self, other): # autoinstantiate right hand side if isinstance(other, type) and issubclass(other, (Table, Display)): other = other() # treat simple strings and functions as ``ieval`` instances elif not isinstance(other, Display) and not isinstance(other, Table): other = ieval(other) # forward operations to the right hand side return other.__ror__(self) def __add__(self, other): # autoinstantiate right hand side if isinstance(other, type) and issubclass(other, Table): other = other() return ichain(self, other) def __radd__(self, other): # autoinstantiate left hand side if isinstance(other, type) and issubclass(other, Table): other = other() return ichain(other, self) def __iter__(self): return xiter(self, "default") class Pipe(Table): """ A ``Pipe`` is an object that can be used in a pipeline expression. It processes the objects it gets from its input ``Table``/``Pipe``. Note that a ``Pipe`` object can't be used as the first object in a pipeline expression, as it doesn't produces items itself. """ class __metaclass__(Table.__metaclass__): def __ror__(self, input): return input | self() def __ror__(self, input): # autoinstantiate left hand side if isinstance(input, type) and issubclass(input, Table): input = input() self.input = input return self def xrepr(item, mode="default"): try: func = item.__xrepr__ except AttributeError: pass else: try: for x in func(mode): yield x except (KeyboardInterrupt, SystemExit): 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__ 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))) 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))) 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) else: yield (astyle.style_default, repr(item)) 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": for attrname in dir(item): yield AttributeDescriptor(attrname) else: yield selfdescriptor else: for attr in func(mode): yield upgradexattr(attr) 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 _isdict(item): def items(item): fields = ("key", "value") for (key, value) in item.iteritems(): yield Fields(fields, key=key, value=value) return items(item) elif isinstance(item, new.module): def items(item): fields = ("key", "value") for key in sorted(item.__dict__): yield Fields(fields, key=key, value=getattr(item, key)) return items(item) 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) else: return iter(func(mode)) # iter() just to be safe class ichain(Pipe): """ Chains multiple ``Table``s into one. """ def __init__(self, *iters): self.iters = iters def __iter__(self): return itertools.chain(*self.iters) def __xrepr__(self, mode): if mode == "header" or mode == "footer": for (i, item) in enumerate(self.iters): if i: yield (astyle.style_default, "+") if isinstance(item, Pipe): yield (astyle.style_default, "(") for part in xrepr(item, mode): yield part if isinstance(item, Pipe): yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) def __repr__(self): args = ", ".join([repr(it) for it in self.iters]) return "%s.%s(%s)" % \ (self.__class__.__module__, self.__class__.__name__, args) class ifile(path.path): """ file (or directory) object. """ def getmode(self): return self.stat().st_mode mode = property(getmode, None, None, "Access mode") def gettype(self): data = [ (stat.S_ISREG, "file"), (stat.S_ISDIR, "dir"), (stat.S_ISCHR, "chardev"), (stat.S_ISBLK, "blockdev"), (stat.S_ISFIFO, "fifo"), (stat.S_ISLNK, "symlink"), (stat.S_ISSOCK,"socket"), ] lstat = self.lstat() if lstat is not None: types = set([text for (func, text) in data if func(lstat.st_mode)]) else: types = set() m = self.mode types.update([text for (func, text) in data if func(m)]) return ", ".join(types) type = property(gettype, None, None, "file type (file, directory, link, etc.)") def getmodestr(self): m = self.mode data = [ (stat.S_IRUSR, "-r"), (stat.S_IWUSR, "-w"), (stat.S_IXUSR, "-x"), (stat.S_IRGRP, "-r"), (stat.S_IWGRP, "-w"), (stat.S_IXGRP, "-x"), (stat.S_IROTH, "-r"), (stat.S_IWOTH, "-w"), (stat.S_IXOTH, "-x"), ] return "".join([text[bool(m&bit)] for (bit, text) in data]) modestr = property(getmodestr, None, None, "Access mode as string") def getblocks(self): return self.stat().st_blocks blocks = property(getblocks, None, None, "File size in blocks") def getblksize(self): return self.stat().st_blksize blksize = property(getblksize, None, None, "Filesystem block size") def getdev(self): return self.stat().st_dev dev = property(getdev) def getnlink(self): return self.stat().st_nlink nlink = property(getnlink, None, None, "Number of links") def getuid(self): return self.stat().st_uid uid = property(getuid, None, None, "User id of file owner") def getgid(self): return self.stat().st_gid gid = property(getgid, None, None, "Group id of file owner") def getowner(self): stat = self.stat() try: return pwd.getpwuid(stat.st_uid).pw_name except KeyError: return stat.st_uid owner = property(getowner, None, None, "Owner name (or id)") def getgroup(self): stat = self.stat() try: return grp.getgrgid(stat.st_gid).gr_name except KeyError: return stat.st_gid group = property(getgroup, None, None, "Group name (or id)") def getadate(self): return datetime.datetime.utcfromtimestamp(self.atime) adate = property(getadate, None, None, "Access date") def getcdate(self): return datetime.datetime.utcfromtimestamp(self.ctime) cdate = property(getcdate, None, None, "Creation date") def getmdate(self): return datetime.datetime.utcfromtimestamp(self.mtime) mdate = property(getmdate, None, None, "Modification date") def mimetype(self): """ Return MIME type guessed from the extension. """ return mimetypes.guess_type(self.basename())[0] def encoding(self): """ Return guessed compression (like "compress" or "gzip"). """ return mimetypes.guess_type(self.basename())[1] 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()", "-listdir()", "-dirs()", "-files()", "-walk()", "-walkdirs()", "-walkfiles()", ) 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() class iparentdir(ifile): def __xrepr__(self, mode): if mode == "cell": yield (astyle.style_dir, os.pardir) else: for part in ifile.__xrepr__(self, mode): yield part class ils(Table): """ List the current (or a specific) directory. Examples: >>> ils >>> ils("/usr/local/lib/python2.4") >>> ils("~") """ def __init__(self, base=os.curdir, dirs=True, files=True): self.base = os.path.expanduser(base) self.dirs = dirs self.files = files def __iter__(self): for child in ifile(self.base).listdir(): if self.dirs: if self.files: yield child else: if child.isdir(): yield child elif self.files: if not child.isdir(): yield child def __xrepr__(self, mode): return ifile(self.base).__xrepr__(mode) def __repr__(self): return "%s.%s(%r)" % \ (self.__class__.__module__, self.__class__.__name__, self.base) class iglob(Table): """ List all files and directories matching a specified pattern. (See ``glob.glob()`` for more info.). Examples: >>> iglob("*.py") """ def __init__(self, glob): self.glob = glob def __iter__(self): for name in glob.glob(self.glob): yield ifile(name) def __xrepr__(self, mode): if mode == "header" or mode == "footer" or mode == "cell": yield (astyle.style_default, "%s(%r)" % (self.__class__.__name__, self.glob)) else: yield (astyle.style_default, repr(self)) def __repr__(self): return "%s.%s(%r)" % \ (self.__class__.__module__, self.__class__.__name__, self.glob) class iwalk(Table): """ List all files and directories in a directory and it's subdirectory. >>> iwalk >>> iwalk("/usr/local/lib/python2.4") >>> iwalk("~") """ def __init__(self, base=os.curdir, dirs=True, files=True): self.base = os.path.expanduser(base) self.dirs = dirs self.files = files def __iter__(self): for (dirpath, dirnames, filenames) in os.walk(self.base): if self.dirs: for name in sorted(dirnames): yield ifile(os.path.join(dirpath, name)) if self.files: for name in sorted(filenames): yield ifile(os.path.join(dirpath, name)) def __xrepr__(self, mode): if mode == "header" or mode == "footer" or mode == "cell": yield (astyle.style_default, "%s(%r)" % (self.__class__.__name__, self.base)) else: yield (astyle.style_default, repr(self)) def __repr__(self): return "%s.%s(%r)" % \ (self.__class__.__module__, self.__class__.__name__, self.base) class ipwdentry(object): """ ``ipwdentry`` objects encapsulate entries in the Unix user account and password database. """ def __init__(self, id): self._id = id self._entry = None def _getentry(self): if self._entry is None: if isinstance(self._id, basestring): self._entry = pwd.getpwnam(self._id) else: self._entry = pwd.getpwuid(self._id) return self._entry def getname(self): if isinstance(self._id, basestring): return self._id else: return self._getentry().pw_name name = property(getname, None, None, "User name") def getpasswd(self): return self._getentry().pw_passwd passwd = property(getpasswd, None, None, "Password") def getuid(self): if isinstance(self._id, basestring): return self._getentry().pw_uid else: return self._id uid = property(getuid, None, None, "User id") def getgid(self): return self._getentry().pw_gid gid = property(getgid, None, None, "Primary group id") def getgroup(self): return igrpentry(self.gid) group = property(getgroup, None, None, "Group") def getgecos(self): return self._getentry().pw_gecos gecos = property(getgecos, None, None, "Information (e.g. full user name)") def getdir(self): return self._getentry().pw_dir dir = property(getdir, None, None, "$HOME directory") def getshell(self): return self._getentry().pw_shell shell = property(getshell, None, None, "Login shell") def __xattrs__(self, mode): return ("name", "passwd", "uid", "gid", "gecos", "dir", "shell") def __repr__(self): return "%s.%s(%r)" % \ (self.__class__.__module__, self.__class__.__name__, self._id) class ipwd(Table): """ List all entries in the Unix user account and password database. Example: >>> ipwd | isort("uid") """ def __iter__(self): for entry in pwd.getpwall(): yield ipwdentry(entry.pw_name) def __xrepr__(self, mode): if mode == "header" or mode == "footer" or mode == "cell": yield (astyle.style_default, "%s()" % self.__class__.__name__) else: yield (astyle.style_default, repr(self)) class igrpentry(object): """ ``igrpentry`` objects encapsulate entries in the Unix group database. """ def __init__(self, id): self._id = id self._entry = None def _getentry(self): if self._entry is None: if isinstance(self._id, basestring): self._entry = grp.getgrnam(self._id) else: self._entry = grp.getgrgid(self._id) return self._entry def getname(self): if isinstance(self._id, basestring): return self._id else: return self._getentry().gr_name name = property(getname, None, None, "Group name") def getpasswd(self): return self._getentry().gr_passwd passwd = property(getpasswd, None, None, "Password") def getgid(self): if isinstance(self._id, basestring): return self._getentry().gr_gid else: return self._id gid = property(getgid, None, None, "Group id") def getmem(self): return self._getentry().gr_mem mem = property(getmem, None, None, "Members") def __xattrs__(self, mode): return ("name", "passwd", "gid", "mem") def __xrepr__(self, mode): if mode == "header" or mode == "footer" or mode == "cell": yield (astyle.style_default, "group ") try: yield (astyle.style_default, self.name) except KeyError: if isinstance(self._id, basestring): yield (astyle.style_default, self.name_id) else: yield (astyle.style_type_number, str(self._id)) else: yield (astyle.style_default, repr(self)) def __iter__(self): for member in self.mem: yield ipwdentry(member) def __repr__(self): return "%s.%s(%r)" % \ (self.__class__.__module__, self.__class__.__name__, self._id) class igrp(Table): """ This ``Table`` lists all entries in the Unix group database. """ def __iter__(self): for entry in grp.getgrall(): yield igrpentry(entry.gr_name) def __xrepr__(self, mode): if mode == "header" or mode == "footer": yield (astyle.style_default, "%s()" % self.__class__.__name__) else: yield (astyle.style_default, repr(self)) class Fields(object): def __init__(self, fieldnames, **fields): self.__fieldnames = [upgradexattr(fieldname) for fieldname in fieldnames] for (key, value) in fields.iteritems(): setattr(self, key, value) def __xattrs__(self, mode): return self.__fieldnames def __xrepr__(self, mode): yield (-1, False) if mode == "header" or mode == "cell": yield (astyle.style_default, self.__class__.__name__) yield (astyle.style_default, "(") for (i, f) in enumerate(self.__fieldnames): if i: yield (astyle.style_default, ", ") yield (astyle.style_default, f.name()) yield (astyle.style_default, "=") for part in xrepr(getattr(self, f), "default"): yield part yield (astyle.style_default, ")") elif mode == "footer": yield (astyle.style_default, self.__class__.__name__) yield (astyle.style_default, "(") for (i, f) in enumerate(self.__fieldnames): if i: yield (astyle.style_default, ", ") yield (astyle.style_default, f.name()) yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) class FieldTable(Table, list): def __init__(self, *fields): Table.__init__(self) list.__init__(self) self.fields = fields def add(self, **fields): self.append(Fields(self.fields, **fields)) def __xrepr__(self, mode): yield (-1, False) if mode == "header" or mode == "footer": yield (astyle.style_default, self.__class__.__name__) yield (astyle.style_default, "(") for (i, f) in enumerate(self.__fieldnames): if i: yield (astyle.style_default, ", ") yield (astyle.style_default, f) yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) def __repr__(self): return "<%s.%s object with fields=%r at 0x%x>" % \ (self.__class__.__module__, self.__class__.__name__, ", ".join(map(repr, self.fields)), id(self)) class List(list): def __xattrs__(self, mode): return xrange(len(self)) def __xrepr__(self, mode): yield (-1, False) if mode == "header" or mode == "cell" or mode == "footer" or mode == "default": yield (astyle.style_default, self.__class__.__name__) yield (astyle.style_default, "(") for (i, item) in enumerate(self): if i: yield (astyle.style_default, ", ") for part in xrepr(item, "default"): yield part yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) class ienv(Table): """ List environment variables. Example: >>> ienv """ def __iter__(self): fields = ("key", "value") for (key, value) in os.environ.iteritems(): yield Fields(fields, key=key, value=value) def __xrepr__(self, mode): if mode == "header" or mode == "cell": yield (astyle.style_default, "%s()" % self.__class__.__name__) else: yield (astyle.style_default, repr(self)) class icsv(Pipe): """ This ``Pipe`` lists turn the input (with must be a pipe outputting lines or an ``ifile``) into lines of CVS columns. """ def __init__(self, **csvargs): """ Create an ``icsv`` object. ``cvsargs`` will be passed through as keyword arguments to ``cvs.reader()``. """ self.csvargs = csvargs def __iter__(self): input = self.input if isinstance(input, ifile): input = input.open("rb") reader = csv.reader(input, **self.csvargs) for line in reader: yield List(line) def __xrepr__(self, mode): yield (-1, False) if mode == "header" or mode == "footer": input = getattr(self, "input", None) if input is not None: for part in xrepr(input, mode): yield part yield (astyle.style_default, " | ") yield (astyle.style_default, "%s(" % self.__class__.__name__) for (i, (name, value)) in enumerate(self.csvargs.iteritems()): if i: yield (astyle.style_default, ", ") yield (astyle.style_default, name) yield (astyle.style_default, "=") for part in xrepr(value, "default"): yield part yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) def __repr__(self): args = ", ".join(["%s=%r" % item for item in self.csvargs.iteritems()]) return "<%s.%s %s at 0x%x>" % \ (self.__class__.__module__, self.__class__.__name__, args, id(self)) class ix(Table): """ Execute a system command and list its output as lines (similar to ``os.popen()``). Examples: >>> ix("ps x") >>> ix("find .") | ifile """ def __init__(self, cmd): self.cmd = cmd self._pipeout = None def __iter__(self): (_pipein, self._pipeout) = os.popen4(self.cmd) _pipein.close() for l in self._pipeout: yield l.rstrip("\r\n") self._pipeout.close() self._pipeout = None def __del__(self): if self._pipeout is not None and not self._pipeout.closed: self._pipeout.close() self._pipeout = None def __xrepr__(self, mode): if mode == "header" or mode == "footer": yield (astyle.style_default, "%s(%r)" % (self.__class__.__name__, self.cmd)) else: yield (astyle.style_default, repr(self)) def __repr__(self): return "%s.%s(%r)" % \ (self.__class__.__module__, self.__class__.__name__, self.cmd) class ifilter(Pipe): """ Filter an input pipe. Only objects where an expression evaluates to true (and doesn't raise an exception) are listed. Examples: >>> ils | ifilter("_.isfile() and size>1000") >>> igrp | ifilter("len(mem)") >>> sys.modules | ifilter(lambda _:_.value is not None) """ def __init__(self, expr, globals=None, errors="raiseifallfail"): """ Create an ``ifilter`` object. ``expr`` can be a callable or a string containing an expression. ``globals`` will be used as the global namespace for calling string expressions (defaulting to IPython's user namespace). ``errors`` specifies how exception during evaluation of ``expr`` are handled: * ``drop``: drop all items that have errors; * ``keep``: keep all items that have errors; * ``keeperror``: keep the exception of all items that have errors; * ``raise``: raise the exception; * ``raiseifallfail``: raise the first exception if all items have errors; otherwise drop those with errors (this is the default). """ self.expr = expr self.globals = globals self.errors = errors def __iter__(self): if callable(self.expr): test = self.expr else: g = getglobals(self.globals) expr = compile(self.expr, "ipipe-expression", "eval") def test(item): return eval(expr, g, AttrNamespace(item)) ok = 0 exc_info = None for item in xiter(self.input): try: if test(item): yield item ok += 1 except (KeyboardInterrupt, SystemExit): raise except Exception, exc: if self.errors == "drop": pass # Ignore errors elif self.errors == "keep": yield item elif self.errors == "keeperror": yield exc elif self.errors == "raise": raise elif self.errors == "raiseifallfail": if exc_info is None: exc_info = sys.exc_info() if not ok and exc_info is not None: raise exc_info[0], exc_info[1], exc_info[2] def __xrepr__(self, mode): if mode == "header" or mode == "footer": input = getattr(self, "input", None) if input is not None: for part in xrepr(input, mode): yield part yield (astyle.style_default, " | ") yield (astyle.style_default, "%s(" % self.__class__.__name__) for part in xrepr(self.expr, "default"): yield part yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) def __repr__(self): return "<%s.%s expr=%r at 0x%x>" % \ (self.__class__.__module__, self.__class__.__name__, self.expr, id(self)) class ieval(Pipe): """ Evaluate an expression for each object in the input pipe. Examples: >>> ils | ieval("_.abspath()") >>> sys.path | ieval(ifile) """ def __init__(self, expr, globals=None, errors="raiseifallfail"): """ Create an ``ieval`` object. ``expr`` can be a callable or a string containing an expression. For the meaning of ``globals`` and ``errors`` see ``ifilter``. """ self.expr = expr self.globals = globals self.errors = errors def __iter__(self): if callable(self.expr): do = self.expr else: g = getglobals(self.globals) expr = compile(self.expr, "ipipe-expression", "eval") def do(item): return eval(expr, g, AttrNamespace(item)) ok = 0 exc_info = None for item in xiter(self.input): try: yield do(item) except (KeyboardInterrupt, SystemExit): raise except Exception, exc: if self.errors == "drop": pass # Ignore errors elif self.errors == "keep": yield item elif self.errors == "keeperror": yield exc elif self.errors == "raise": raise elif self.errors == "raiseifallfail": if exc_info is None: exc_info = sys.exc_info() if not ok and exc_info is not None: raise exc_info[0], exc_info[1], exc_info[2] def __xrepr__(self, mode): if mode == "header" or mode == "footer": input = getattr(self, "input", None) if input is not None: for part in xrepr(input, mode): yield part yield (astyle.style_default, " | ") yield (astyle.style_default, "%s(" % self.__class__.__name__) for part in xrepr(self.expr, "default"): yield part yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) def __repr__(self): return "<%s.%s expr=%r at 0x%x>" % \ (self.__class__.__module__, self.__class__.__name__, self.expr, id(self)) class ienum(Pipe): """ Enumerate the input pipe (i.e. wrap each input object in an object with ``index`` and ``object`` attributes). Examples: >>> xrange(20) | ieval("_,_*_") | ienum | ifilter("index % 2 == 0") | ieval("object") """ def __iter__(self): fields = ("index", "object") for (index, object) in enumerate(xiter(self.input)): yield Fields(fields, index=index, object=object) class isort(Pipe): """ Sorts the input pipe. Examples: >>> ils | isort("size") >>> ils | isort("_.isdir(), _.lower()", reverse=True) """ def __init__(self, key=None, globals=None, reverse=False): """ Create an ``isort`` object. ``key`` can be a callable or a string containing an expression (or ``None`` in which case the items themselves will be sorted). If ``reverse`` is true the sort order will be reversed. For the meaning of ``globals`` see ``ifilter``. """ self.key = key self.globals = globals self.reverse = reverse def __iter__(self): if self.key is None: items = sorted(xiter(self.input), reverse=self.reverse) elif callable(self.key): items = sorted(xiter(self.input), key=self.key, reverse=self.reverse) else: g = getglobals(self.globals) key = compile(self.key, "ipipe-expression", "eval") def realkey(item): return eval(key, g, AttrNamespace(item)) items = sorted(xiter(self.input), key=realkey, reverse=self.reverse) for item in items: yield item def __xrepr__(self, mode): if mode == "header" or mode == "footer": input = getattr(self, "input", None) if input is not None: for part in xrepr(input, mode): yield part yield (astyle.style_default, " | ") yield (astyle.style_default, "%s(" % self.__class__.__name__) for part in xrepr(self.key, "default"): yield part if self.reverse: yield (astyle.style_default, ", ") for part in xrepr(True, "default"): yield part yield (astyle.style_default, ")") else: yield (astyle.style_default, repr(self)) def __repr__(self): return "<%s.%s key=%r reverse=%r at 0x%x>" % \ (self.__class__.__module__, self.__class__.__name__, self.key, self.reverse, id(self)) tab = 3 # for expandtabs() def _format(field): if isinstance(field, str): text = repr(field.expandtabs(tab))[1:-1] elif isinstance(field, unicode): text = repr(field.expandtabs(tab))[2:-1] elif isinstance(field, datetime.datetime): # Don't use strftime() here, as this requires year >= 1900 text = "%04d-%02d-%02d %02d:%02d:%02d.%06d" % \ (field.year, field.month, field.day, field.hour, field.minute, field.second, field.microsecond) elif isinstance(field, datetime.date): text = "%04d-%02d-%02d" % (field.year, field.month, field.day) else: text = repr(field) return text class Display(object): class __metaclass__(type): def __ror__(self, input): return input | self() def __ror__(self, input): self.input = input return self def display(self): pass class iless(Display): cmd = "less --quit-if-one-screen --LONG-PROMPT --LINE-NUMBERS --chop-long-lines --shift=8 --RAW-CONTROL-CHARS" def display(self): 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)) pager.write("\n") finally: pager.close() except Exception, exc: print "%s: %s" % (exc.__class__.__name__, str(exc)) def xformat(value, mode, maxlength): align = None full = True width = 0 text = astyle.Text() for (style, part) in xrepr(value, mode): # only consider the first result if align is None: if isinstance(style, int): # (style, text) really is (alignment, stop) align = style full = part continue else: align = -1 full = True if not isinstance(style, int): text.append((style, part)) width += len(part) if width >= maxlength and not full: text.append((astyle.style_ellisis, "...")) width += 3 break if align is None: # default to left alignment align = -1 return (align, width, text) class idump(Display): # The approximate maximum length of a column entry maxattrlength = 200 # Style for column names style_header = astyle.Style.fromstr("white:black:bold") def __init__(self, *attrs): self.attrs = [upgradexattr(attr) for attr in attrs] self.headerpadchar = " " self.headersepchar = "|" self.datapadchar = " " self.datasepchar = "|" def display(self): stream = genutils.Term.cout allattrs = [] attrset = set() colwidths = {} rows = [] for item in xiter(self.input, "default"): row = {} attrs = self.attrs if not attrs: attrs = xattrs(item, "default") for attr in attrs: if attr not in attrset: allattrs.append(attr) attrset.add(attr) colwidths[attr] = len(attr.name()) try: value = attr.value(item) except (KeyboardInterrupt, SystemExit): raise except Exception, exc: value = exc (align, width, text) = xformat(value, "cell", self.maxattrlength) colwidths[attr] = max(colwidths[attr], width) # remember alignment, length and colored parts row[attr] = (align, width, text) rows.append(row) stream.write("\n") 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, 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[attr] - width spc1 = spc//2 spc2 = spc-spc1 stream.write(self.datapadchar*spc1) text.write(stream) if i < len(colwidths)-1: stream.write(self.datapadchar*spc2) else: stream.write(self.datapadchar*spc) text.write(stream) if i < len(colwidths)-1: stream.write(self.datasepchar) stream.write("\n") class XMode(object): """ An ``XMode`` object describes one enter mode available for an object """ 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 def __iter__(self): return self.descriptor.iter(self.object) 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): 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: from ibrowse import ibrowse except ImportError: # No curses (probably Windows) => use ``idump`` as the default display. defaultdisplay = idump else: defaultdisplay = ibrowse __all__.append("ibrowse") # If we're running under IPython, install an IPython displayhook that # returns the object from Display.display(), else install a displayhook # directly as sys.displayhook api = None if ipapi is not None: try: api = ipapi.get() except AttributeError: pass if api is not None: def displayhook(self, obj): if isinstance(obj, type) and issubclass(obj, Table): obj = obj() if isinstance(obj, Table): obj = obj | defaultdisplay if isinstance(obj, Display): return obj.display() else: raise ipapi.TryNext api.set_hook("result_display", displayhook) else: def installdisplayhook(): _originalhook = sys.displayhook def displayhook(obj): if isinstance(obj, type) and issubclass(obj, Table): obj = obj() if isinstance(obj, Table): obj = obj | defaultdisplay if isinstance(obj, Display): return obj.display() else: _originalhook(obj) sys.displayhook = displayhook installdisplayhook()