Added a simple copy function for cursors.
[automanga.git] / manga / lib.py
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."""
119
120     def __enter__(self):
121         return self
122
123     def __exit__(self, *exc_info):
124         self.close()
125
126     def close(self):
127         """Close this stream."""
128         raise NotImplementedError()
129
130     def read(self, sz = None):
131         """Read SZ bytes from the stream, or the entire rest of the
132         stream of SZ is not given."""
133         raise NotImplementedError()
134
135 class cursor(object):
136     def __init__(self, ob):
137         if isinstance(ob, cursor):
138             self.cur = ob.cur
139         else:
140             self.cur = self.descend(ob)
141
142     def descend(self, ob):
143         while isinstance(ob, pagelist):
144             ob = ob[0]
145         if not isinstance(ob, page):
146             raise TypeError("object in page tree was unexpectedly not a pagetree")
147         return ob
148
149     def next(self):
150         for n, i in reversed(self.cur.stack):
151             if i < len(n) - 1:
152                 self.cur = self.descend(n[i + 1])
153                 return self.cur
154         raise StopIteration()
155
156     def prev(self):
157         for n, i in reversed(self.cur,stack):
158             if i > 0:
159                 self.cur = self.descend(n[i - 1])
160                 return self.cur
161         raise StopIteration()
162
163     def __iter__(self):
164         return self