blob: ff7dbf6b14b39dd262d537f059301cf965ef9061 [file] [log] [blame]
Guido van Rossum54f22ed2000-02-04 15:10:34 +00001"""A readline()-style interface to the parts of a multipart message.
2
3The MultiFile class makes each part of a multipart message "feel" like
4an ordinary file, as long as you use fp.readline(). Allows recursive
5use, for nested multipart messages. Probably best used together
6with module mimetools.
7
8Suggested use:
9
10real_fp = open(...)
11fp = MultiFile(real_fp)
12
13"read some lines from fp"
14fp.push(separator)
15while 1:
Tim Peters07e99cb2001-01-14 23:47:14 +000016 "read lines from fp until it returns an empty string" (A)
17 if not fp.next(): break
Guido van Rossum54f22ed2000-02-04 15:10:34 +000018fp.pop()
19"read remaining lines from fp until it returns an empty string"
20
21The latter sequence may be used recursively at (A).
22It is also allowed to use multiple push()...pop() sequences.
23
Thomas Wouters7e474022000-07-16 12:04:32 +000024If seekable is given as 0, the class code will not do the bookkeeping
Guido van Rossum54f22ed2000-02-04 15:10:34 +000025it normally attempts in order to make seeks relative to the beginning of the
26current file part. This may be useful when using MultiFile with a non-
27seekable stream object.
28"""
Guido van Rossum741c81a1992-07-13 14:40:45 +000029
Skip Montanaro269b83b2001-02-06 01:07:02 +000030__all__ = ["MultiFile","Error"]
31
Fred Drake227b1202000-08-17 05:06:49 +000032class Error(Exception):
Tim Peters07e99cb2001-01-14 23:47:14 +000033 pass
Guido van Rossum741c81a1992-07-13 14:40:45 +000034
35class MultiFile:
Guido van Rossum54f22ed2000-02-04 15:10:34 +000036
Tim Peters07e99cb2001-01-14 23:47:14 +000037 seekable = 0
Guido van Rossum54f22ed2000-02-04 15:10:34 +000038
Tim Peters07e99cb2001-01-14 23:47:14 +000039 def __init__(self, fp, seekable=1):
40 self.fp = fp
41 self.stack = [] # Grows down
42 self.level = 0
43 self.last = 0
Guido van Rossum912e56c2001-09-18 14:34:06 +000044 self.readahead = ""
Tim Peters07e99cb2001-01-14 23:47:14 +000045 if seekable:
46 self.seekable = 1
47 self.start = self.fp.tell()
48 self.posstack = [] # Grows down
Guido van Rossum54f22ed2000-02-04 15:10:34 +000049
Tim Peters07e99cb2001-01-14 23:47:14 +000050 def tell(self):
51 if self.level > 0:
52 return self.lastpos
Guido van Rossum912e56c2001-09-18 14:34:06 +000053 return self.fp.tell() - len(self.readahead) - self.start
Guido van Rossum54f22ed2000-02-04 15:10:34 +000054
Tim Peters07e99cb2001-01-14 23:47:14 +000055 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
Guido van Rossum912e56c2001-09-18 14:34:06 +000071 self.readahead = ""
Guido van Rossum54f22ed2000-02-04 15:10:34 +000072
Tim Peters07e99cb2001-01-14 23:47:14 +000073 def readline(self):
Guido van Rossum912e56c2001-09-18 14:34:06 +000074 if not self.readahead:
75 self.readahead = self._readline()
76 line = self.readahead
77 if line:
78 self.readahead = self._readline()
Guido van Rossum39c78512001-10-05 21:22:21 +000079 if not self.readahead:
80 if line[-2:] == "\r\n":
81 line = line[:-2]
82 elif line[-1:] == "\n":
83 line = line[:-1]
Guido van Rossum912e56c2001-09-18 14:34:06 +000084 return line
85
86 def _readline(self):
Tim Peters07e99cb2001-01-14 23:47:14 +000087 if self.level > 0:
88 return ''
89 line = self.fp.readline()
90 # Real EOF?
91 if not line:
92 self.level = len(self.stack)
93 self.last = (self.level > 0)
94 if self.last:
95 raise Error, 'sudden EOF in MultiFile.readline()'
96 return ''
97 assert self.level == 0
98 # Fast check to see if this is just data
99 if self.is_data(line):
100 return line
101 else:
102 # Ignore trailing whitespace on marker lines
Eric S. Raymonde37340e2001-02-09 16:56:44 +0000103 marker = line.rstrip()
Tim Peters07e99cb2001-01-14 23:47:14 +0000104 # No? OK, try to match a boundary.
105 # Return the line (unstripped) if we don't.
106 for i in range(len(self.stack)):
107 sep = self.stack[i]
108 if marker == self.section_divider(sep):
109 self.last = 0
110 break
111 elif marker == self.end_marker(sep):
112 self.last = 1
113 break
114 else:
115 return line
116 # We only get here if we see a section divider or EOM line
117 if self.seekable:
118 self.lastpos = self.tell() - len(line)
119 self.level = i+1
120 if self.level > 1:
121 raise Error,'Missing endmarker in MultiFile.readline()'
122 return ''
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000123
Tim Peters07e99cb2001-01-14 23:47:14 +0000124 def readlines(self):
125 list = []
126 while 1:
127 line = self.readline()
128 if not line: break
129 list.append(line)
130 return list
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000131
Tim Peters07e99cb2001-01-14 23:47:14 +0000132 def read(self): # Note: no size argument -- read until EOF only!
Fred Drake521c83d2001-03-12 02:56:15 +0000133 return ''.join(self.readlines())
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000134
Tim Peters07e99cb2001-01-14 23:47:14 +0000135 def next(self):
136 while self.readline(): pass
137 if self.level > 1 or self.last:
138 return 0
139 self.level = 0
140 self.last = 0
141 if self.seekable:
142 self.start = self.fp.tell()
143 return 1
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000144
Tim Peters07e99cb2001-01-14 23:47:14 +0000145 def push(self, sep):
146 if self.level > 0:
147 raise Error, 'bad MultiFile.push() call'
148 self.stack.insert(0, sep)
149 if self.seekable:
150 self.posstack.insert(0, self.start)
151 self.start = self.fp.tell()
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000152
Tim Peters07e99cb2001-01-14 23:47:14 +0000153 def pop(self):
154 if self.stack == []:
155 raise Error, 'bad MultiFile.pop() call'
156 if self.level <= 1:
157 self.last = 0
158 else:
159 abslastpos = self.lastpos + self.start
160 self.level = max(0, self.level - 1)
161 del self.stack[0]
162 if self.seekable:
163 self.start = self.posstack[0]
164 del self.posstack[0]
165 if self.level > 0:
166 self.lastpos = abslastpos - self.start
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000167
Tim Peters07e99cb2001-01-14 23:47:14 +0000168 def is_data(self, line):
169 return line[:2] != '--'
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000170
Tim Peters07e99cb2001-01-14 23:47:14 +0000171 def section_divider(self, str):
172 return "--" + str
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000173
Tim Peters07e99cb2001-01-14 23:47:14 +0000174 def end_marker(self, str):
175 return "--" + str + "--"