This commit was manufactured by cvs2svn to create tag 'r22a4-fork'.
[python/dscho.git] / Lib / multifile.py
blob47e2346978a9ffac2ff0e289f884f67a04bd8a51
1 """A readline()-style interface to the parts of a multipart message.
3 The MultiFile class makes each part of a multipart message "feel" like
4 an ordinary file, as long as you use fp.readline(). Allows recursive
5 use, for nested multipart messages. Probably best used together
6 with module mimetools.
8 Suggested use:
10 real_fp = open(...)
11 fp = MultiFile(real_fp)
13 "read some lines from fp"
14 fp.push(separator)
15 while 1:
16 "read lines from fp until it returns an empty string" (A)
17 if not fp.next(): break
18 fp.pop()
19 "read remaining lines from fp until it returns an empty string"
21 The latter sequence may be used recursively at (A).
22 It is also allowed to use multiple push()...pop() sequences.
24 If seekable is given as 0, the class code will not do the bookkeeping
25 it normally attempts in order to make seeks relative to the beginning of the
26 current file part. This may be useful when using MultiFile with a non-
27 seekable stream object.
28 """
30 __all__ = ["MultiFile","Error"]
32 class Error(Exception):
33 pass
35 class MultiFile:
37 seekable = 0
39 def __init__(self, fp, seekable=1):
40 self.fp = fp
41 self.stack = [] # Grows down
42 self.level = 0
43 self.last = 0
44 self.readahead = ""
45 if seekable:
46 self.seekable = 1
47 self.start = self.fp.tell()
48 self.posstack = [] # Grows down
50 def tell(self):
51 if self.level > 0:
52 return self.lastpos
53 return self.fp.tell() - len(self.readahead) - self.start
55 def seek(self, pos, whence=0):
56 here = self.tell()
57 if whence:
58 if whence == 1:
59 pos = pos + here
60 elif whence == 2:
61 if self.level > 0:
62 pos = pos + self.lastpos
63 else:
64 raise Error, "can't use whence=2 yet"
65 if not 0 <= pos <= here or \
66 self.level > 0 and pos > self.lastpos:
67 raise Error, 'bad MultiFile.seek() call'
68 self.fp.seek(pos + self.start)
69 self.level = 0
70 self.last = 0
71 self.readahead = ""
73 def readline(self):
74 if not self.readahead:
75 self.readahead = self._readline()
76 line = self.readahead
77 if line:
78 self.readahead = self._readline()
79 if not self.readahead and line[-1:] == "\n":
80 line = line[:-1]
81 return line
83 def _readline(self):
84 if self.level > 0:
85 return ''
86 line = self.fp.readline()
87 # Real EOF?
88 if not line:
89 self.level = len(self.stack)
90 self.last = (self.level > 0)
91 if self.last:
92 raise Error, 'sudden EOF in MultiFile.readline()'
93 return ''
94 assert self.level == 0
95 # Fast check to see if this is just data
96 if self.is_data(line):
97 return line
98 else:
99 # Ignore trailing whitespace on marker lines
100 marker = line.rstrip()
101 # No? OK, try to match a boundary.
102 # Return the line (unstripped) if we don't.
103 for i in range(len(self.stack)):
104 sep = self.stack[i]
105 if marker == self.section_divider(sep):
106 self.last = 0
107 break
108 elif marker == self.end_marker(sep):
109 self.last = 1
110 break
111 else:
112 return line
113 # We only get here if we see a section divider or EOM line
114 if self.seekable:
115 self.lastpos = self.tell() - len(line)
116 self.level = i+1
117 if self.level > 1:
118 raise Error,'Missing endmarker in MultiFile.readline()'
119 return ''
121 def readlines(self):
122 list = []
123 while 1:
124 line = self.readline()
125 if not line: break
126 list.append(line)
127 return list
129 def read(self): # Note: no size argument -- read until EOF only!
130 return ''.join(self.readlines())
132 def next(self):
133 while self.readline(): pass
134 if self.level > 1 or self.last:
135 return 0
136 self.level = 0
137 self.last = 0
138 if self.seekable:
139 self.start = self.fp.tell()
140 return 1
142 def push(self, sep):
143 if self.level > 0:
144 raise Error, 'bad MultiFile.push() call'
145 self.stack.insert(0, sep)
146 if self.seekable:
147 self.posstack.insert(0, self.start)
148 self.start = self.fp.tell()
150 def pop(self):
151 if self.stack == []:
152 raise Error, 'bad MultiFile.pop() call'
153 if self.level <= 1:
154 self.last = 0
155 else:
156 abslastpos = self.lastpos + self.start
157 self.level = max(0, self.level - 1)
158 del self.stack[0]
159 if self.seekable:
160 self.start = self.posstack[0]
161 del self.posstack[0]
162 if self.level > 0:
163 self.lastpos = abslastpos - self.start
165 def is_data(self, line):
166 return line[:2] != '--'
168 def section_divider(self, str):
169 return "--" + str
171 def end_marker(self, str):
172 return "--" + str + "--"