| 1 | class library(object): |
| 2 | """Class representing a single source of multiple mangas.""" |
| 3 | |
| 4 | def byname(self, prefix): |
| 5 | """Returns an iterable object of all mangas in this library |
| 6 | whose names (case-insensitively) begin with the given |
| 7 | prefix. |
| 8 | |
| 9 | All libraries should implement this.""" |
| 10 | raise NotImplementedError() |
| 11 | |
| 12 | def byid(self, id): |
| 13 | """Returns a previously known manga by its string ID, or |
| 14 | raises KeyError if no such manga could be found. |
| 15 | |
| 16 | All libraries should implement this.""" |
| 17 | raise KeyError(id) |
| 18 | |
| 19 | def __iter__(self): |
| 20 | """Return an iterator of all known mangas in this library. |
| 21 | |
| 22 | Not all libraries need implement this.""" |
| 23 | raise NotImplementedError("manga.lib.library iterator") |
| 24 | |
| 25 | class pagetree(object): |
| 26 | """Base class for objects in the tree of pages and pagelists. |
| 27 | |
| 28 | All pagetree objects should contain an attribute `stack', |
| 29 | containing a list of pairs. The last pair in the list should be |
| 30 | the pagetree object which yielded this pagetree object, along with |
| 31 | the index which yielded it. Every non-last pair should be the same |
| 32 | information for the pair following it. The only objects with empty |
| 33 | `stack' lists should be `manga' objects. |
| 34 | |
| 35 | All non-root pagetree objects should also contain an attribute |
| 36 | `id', which should be a string that can be passed to the `byid' |
| 37 | function of its parent node to recover the node. Such string ID |
| 38 | should be more persistent than the node's numeric index in the |
| 39 | parent.""" |
| 40 | |
| 41 | def idlist(self): |
| 42 | """Returns a list of the IDs necessary to resolve this node |
| 43 | from the root node.""" |
| 44 | if len(self.stack) == 0: |
| 45 | raise Exception("Cannot get ID list on root node.") |
| 46 | return [n.id for n, i in self.stack[1:]] + [self.id] |
| 47 | |
| 48 | def byidlist(self, idlist): |
| 49 | if len(idlist) == 0: |
| 50 | return self |
| 51 | return self.byid(idlist[0]).byidlist(idlist[1:]) |
| 52 | |
| 53 | class pagelist(pagetree): |
| 54 | """Class representing a list of either pages, or nested |
| 55 | pagelists. Might be, for instance, a volume or a chapter. |
| 56 | |
| 57 | All pagelists should contain an attribute `name', containing some |
| 58 | human-readable Unicode representation of the pagelist.""" |
| 59 | |
| 60 | def __len__(self): |
| 61 | """Return the number of (direct) sub-nodes in this pagelist. |
| 62 | |
| 63 | All pagelists need to implement this.""" |
| 64 | raise NotImplementedError() |
| 65 | |
| 66 | def __getitem__(self, idx): |
| 67 | """Return the direct sub-node of the given index in this |
| 68 | pagelist. Sub-node indexes are always zero-based and |
| 69 | contiguous, regardless of any gaps in the underlying medium, |
| 70 | which should be indicated instead by way of the `name' |
| 71 | attribute. |
| 72 | |
| 73 | All pagelists need to implement this.""" |
| 74 | raise NotImplementedError() |
| 75 | |
| 76 | def byid(self, id): |
| 77 | """Return the direct sub-node of this pagelist which has the |
| 78 | given string ID. If none is found, a KeyError is raised. |
| 79 | |
| 80 | This default method iterates the children of this node, but |
| 81 | may be overridden by some more efficient implementation. |
| 82 | """ |
| 83 | for ch in self: |
| 84 | if ch.id == id: |
| 85 | return ch |
| 86 | raise KeyError(id) |
| 87 | |
| 88 | class manga(pagelist): |
| 89 | """Class reprenting a single manga. Includes the pagelist class, |
| 90 | and all constraints valid for it. |
| 91 | |
| 92 | A manga is a root pagetree node, but should also contain an `id' |
| 93 | attribute, which can be used to recover the manga from its |
| 94 | library's `byid' function.""" |
| 95 | pass |
| 96 | |
| 97 | class page(pagetree): |
| 98 | """Class representing a single page of a manga. Pages make up the |
| 99 | leaf nodes of a pagelist tree. |
| 100 | |
| 101 | All pages should contain an attribute `manga', referring back to |
| 102 | the containing manga instance.""" |
| 103 | |
| 104 | def open(self): |
| 105 | """Open a stream for the image this page represents. The |
| 106 | returned object should be an imgstream class. |
| 107 | |
| 108 | All pages need to implement this.""" |
| 109 | raise NotImplementedError() |
| 110 | |
| 111 | class imgstream(object): |
| 112 | """An open image I/O stream for a manga page. Generally, it should |
| 113 | be file-like. This base class implements the resource-manager |
| 114 | interface for use in `with' statements, calling close() on itself |
| 115 | when exiting the with-scope. |
| 116 | |
| 117 | All imgstreams should contain an attribute `ctype', being the |
| 118 | Content-Type of the image being read by the stream, and `clen`, |
| 119 | being either an int describing the total number of bytes in the |
| 120 | stream, or None if the value is not known in advance.""" |
| 121 | |
| 122 | def __enter__(self): |
| 123 | return self |
| 124 | |
| 125 | def __exit__(self, *exc_info): |
| 126 | self.close() |
| 127 | |
| 128 | def fileno(self): |
| 129 | """If reading the imgstream may block, fileno() should return |
| 130 | a file descriptor that can be polled. If fileno() returns |
| 131 | None, that should mean that reading will not block.""" |
| 132 | return None |
| 133 | |
| 134 | def close(self): |
| 135 | """Close this stream.""" |
| 136 | raise NotImplementedError() |
| 137 | |
| 138 | def read(self, sz = None): |
| 139 | """Read SZ bytes from the stream, or the entire rest of the |
| 140 | stream of SZ is not given.""" |
| 141 | raise NotImplementedError() |
| 142 | |
| 143 | class cursor(object): |
| 144 | def __init__(self, ob): |
| 145 | if isinstance(ob, cursor): |
| 146 | self.cur = ob.cur |
| 147 | else: |
| 148 | self.cur = self.descend(ob) |
| 149 | |
| 150 | def descend(self, ob): |
| 151 | while isinstance(ob, pagelist): |
| 152 | ob = ob[0] |
| 153 | if not isinstance(ob, page): |
| 154 | raise TypeError("object in page tree was unexpectedly not a pagetree") |
| 155 | return ob |
| 156 | |
| 157 | def next(self): |
| 158 | for n, i in reversed(self.cur.stack): |
| 159 | if i < len(n) - 1: |
| 160 | self.cur = self.descend(n[i + 1]) |
| 161 | return self.cur |
| 162 | raise StopIteration() |
| 163 | |
| 164 | def prev(self): |
| 165 | for n, i in reversed(self.cur,stack): |
| 166 | if i > 0: |
| 167 | self.cur = self.descend(n[i - 1]) |
| 168 | return self.cur |
| 169 | raise StopIteration() |
| 170 | |
| 171 | def __iter__(self): |
| 172 | return self |