Added simplistic keyword searching to mrnet and local libraries.
[automanga.git] / manga / lib.py
index 7988438..353d7ad 100644 (file)
 class library(object):
-    pass
+    """Class representing a single source of multiple mangas."""
+    
+    def byname(self, prefix):
+        """Returns an iterable object of all mangas in this library
+        whose names (case-insensitively) begin with the given
+        prefix.
 
-class pagelist(object):
-    pass
+        All libraries should implement this."""
+        raise NotImplementedError()
+
+    def search(self, string):
+        """Returns an iterable object of mangas in this library that
+        matches the search string in a library-dependent manner. While
+        each library is at liberty to define its own matching
+        criteria, it is probably likely to involve something akin to
+        searching for keywords in the titles of the library.
+
+        Searching may return very many results and may be slow to
+        iterate.
+
+        Not all libraries need implement this."""
+        raise NotImplementedError()
+
+    def byid(self, id):
+        """Returns a previously known manga by its string ID, or
+        raises KeyError if no such manga could be found.
+
+        All libraries should implement this."""
+        raise KeyError(id)
+
+    def __iter__(self):
+        """Return an iterator of all known mangas in this library.
+
+        Not all libraries need implement this."""
+        raise NotImplementedError("manga.lib.library iterator")
+
+class pagetree(object):
+    """Base class for objects in the tree of pages and pagelists.
+
+    All pagetree objects should contain an attribute `stack',
+    containing a list of pairs. The last pair in the list should be
+    the pagetree object which yielded this pagetree object, along with
+    the index which yielded it. Every non-last pair should be the same
+    information for the pair following it. The only objects with empty
+    `stack' lists should be `manga' objects.
+    
+    All non-root pagetree objects should also contain an attribute
+    `id', which should be a string that can be passed to the `byid'
+    function of its parent node to recover the node. Such string ID
+    should be more persistent than the node's numeric index in the
+    parent.
+
+    All pagetree objects should contain an attribute `name',
+    containing some human-readable Unicode representation of the
+    pagelist."""
+    
+    def idlist(self):
+        """Returns a list of the IDs necessary to resolve this node
+        from the root node."""
+        if len(self.stack) == 0:
+            return []
+        return self.stack[-1][0].idlist() + [self.id]
+
+    def byidlist(self, idlist):
+        if len(idlist) == 0:
+            return self
+        return self.byid(idlist[0]).byidlist(idlist[1:])
+
+class pagelist(pagetree):
+    """Class representing a list of either pages, or nested
+    pagelists. Might be, for instance, a volume or a chapter."""
+
+    def __len__(self):
+        """Return the number of (direct) sub-nodes in this pagelist.
+
+        All pagelists need to implement this."""
+        raise NotImplementedError()
+
+    def __getitem__(self, idx):
+        """Return the direct sub-node of the given index in this
+        pagelist. Sub-node indexes are always zero-based and
+        contiguous, regardless of any gaps in the underlying medium,
+        which should be indicated instead by way of the `name'
+        attribute.
+
+        All pagelists need to implement this."""
+        raise NotImplementedError()
+
+    def byid(self, id):
+        """Return the direct sub-node of this pagelist which has the
+        given string ID. If none is found, a KeyError is raised.
+
+        This default method iterates the children of this node, but
+        may be overridden by some more efficient implementation.
+        """
+        for ch in self:
+            if ch.id == id:
+                return ch
+        raise KeyError(id)
 
 class manga(pagelist):
-    pass
+    """Class reprenting a single manga. Includes the pagelist class,
+    and all constraints valid for it.
 
-class page(object):
+    A manga is a root pagetree node, but should also contain an `id'
+    attribute, which can be used to recover the manga from its
+    library's `byid' function."""
     pass
 
-class pageiter(object):
-    def __init__(self, root):
-        self.nstack = [0]
-        self.lstack = [root]
+class page(pagetree):
+    """Class representing a single page of a manga. Pages make up the
+    leaf nodes of a pagelist tree.
+
+    All pages should contain an attribute `manga', referring back to
+    the containing manga instance."""
+    
+    def open(self):
+        """Open a stream for the image this page represents. The
+        returned object should be an imgstream class.
+
+        All pages need to implement this."""
+        raise NotImplementedError()
+
+class imgstream(object):
+    """An open image I/O stream for a manga page. Generally, it should
+    be file-like. This base class implements the resource-manager
+    interface for use in `with' statements, calling close() on itself
+    when exiting the with-scope.
+
+    All imgstreams should contain an attribute `ctype', being the
+    Content-Type of the image being read by the stream, and `clen`,
+    being either an int describing the total number of bytes in the
+    stream, or None if the value is not known in advance."""
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *exc_info):
+        self.close()
+
+    def fileno(self):
+        """If reading the imgstream may block, fileno() should return
+        a file descriptor that can be polled. If fileno() returns
+        None, that should mean that reading will not block."""
+        return None
+
+    def close(self):
+        """Close this stream."""
+        raise NotImplementedError()
+
+    def read(self, sz = None):
+        """Read SZ bytes from the stream, or the entire rest of the
+        stream of SZ is not given."""
+        raise NotImplementedError()
+
+class cursor(object):
+    def __init__(self, ob):
+        if isinstance(ob, cursor):
+            self.cur = ob.cur
+        else:
+            self.cur = self.descend(ob)
+
+    def descend(self, ob, last=False):
+        while isinstance(ob, pagelist):
+            ob = ob[len(ob) - 1 if last else 0]
+        if not isinstance(ob, page):
+            raise TypeError("object in page tree was unexpectedly not a pagetree")
+        return ob
 
     def next(self):
-        while True:
-            if len(self.nstack) == 0:
-                raise StopIteration
-            try:
-                node = self.lstack[-1][self.nstack[-1]]
-            except IndexError:
-                self.lstack.pop()
-                self.nstack.pop()
-                if len(self.nstack) > 0:
-                    self.nstack[-1] += 1
-                continue
-            if isinstance(node, page):
-                nl = tuple(self.nstack)
-                self.nstack[-1] += 1
-                return nl, node
-            elif isinstance(node, pagelist):
-                self.lstack.append(node)
-                self.nstack.append(0)
+        for n, i in reversed(self.cur.stack):
+            if i < len(n) - 1:
+                self.cur = self.descend(n[i + 1])
+                return self.cur
+        raise StopIteration()
+
+    def prev(self):
+        for n, i in reversed(self.cur.stack):
+            if i > 0:
+                self.cur = self.descend(n[i - 1], True)
+                return self.cur
+        raise StopIteration()
 
     def __iter__(self):
         return self
+
+loaded = {}
+def findlib(name):
+    def load(name):
+        mod = __import__(name, fromlist=["dummy"])
+        if not hasattr(mod, "library"):
+            raise ImportError("module " + name + " is not a manga library")
+        return mod.library()
+    if name not in loaded:
+        try:
+            loaded[name] = load("manga." + name)
+        except ImportError:
+            loaded[name] = load(name)
+    return loaded[name]