blob: e43d331c57144a43851eaff6c261672bdc75d441 [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:
16 "read lines from fp until it returns an empty string" (A)
17 if not fp.next(): break
18fp.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
30import sys
31import string
32
Fred Drake227b1202000-08-17 05:06:49 +000033class Error(Exception):
34 pass
Guido van Rossum741c81a1992-07-13 14:40:45 +000035
36class MultiFile:
Guido van Rossum54f22ed2000-02-04 15:10:34 +000037
Guido van Rossum4d40b0a1998-06-23 14:20:27 +000038 seekable = 0
Guido van Rossum54f22ed2000-02-04 15:10:34 +000039
Guido van Rossum4d40b0a1998-06-23 14:20:27 +000040 def __init__(self, fp, seekable=1):
Guido van Rossum741c81a1992-07-13 14:40:45 +000041 self.fp = fp
42 self.stack = [] # Grows down
43 self.level = 0
44 self.last = 0
Guido van Rossum4d40b0a1998-06-23 14:20:27 +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
Guido van Rossum741c81a1992-07-13 14:40:45 +000050 def tell(self):
51 if self.level > 0:
52 return self.lastpos
53 return self.fp.tell() - self.start
Guido van Rossum54f22ed2000-02-04 15:10:34 +000054
Guido van Rossum69c70a21998-03-25 16:25:26 +000055 def seek(self, pos, whence=0):
Guido van Rossum8ca84201998-03-26 20:56:10 +000056 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"
Guido van Rossum69c70a21998-03-25 16:25:26 +000065 if not 0 <= pos <= here or \
Guido van Rossum741c81a1992-07-13 14:40:45 +000066 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 Rossum54f22ed2000-02-04 15:10:34 +000071
Guido van Rossum741c81a1992-07-13 14:40:45 +000072 def readline(self):
Guido van Rossum3ad9dd51998-06-29 17:56:06 +000073 if self.level > 0:
74 return ''
Guido van Rossum741c81a1992-07-13 14:40:45 +000075 line = self.fp.readline()
Guido van Rossum3ad9dd51998-06-29 17:56:06 +000076 # Real EOF?
Guido van Rossum741c81a1992-07-13 14:40:45 +000077 if not line:
78 self.level = len(self.stack)
79 self.last = (self.level > 0)
80 if self.last:
Guido van Rossum3ad9dd51998-06-29 17:56:06 +000081 raise Error, 'sudden EOF in MultiFile.readline()'
Guido van Rossum741c81a1992-07-13 14:40:45 +000082 return ''
Guido van Rossum3ad9dd51998-06-29 17:56:06 +000083 assert self.level == 0
84 # Fast check to see if this is just data
85 if self.is_data(line):
86 return line
87 else:
88 # Ignore trailing whitespace on marker lines
89 k = len(line) - 1;
90 while line[k] in string.whitespace:
91 k = k - 1
92 marker = line[:k+1]
93 # No? OK, try to match a boundary.
94 # Return the line (unstripped) if we don't.
Guido van Rossum741c81a1992-07-13 14:40:45 +000095 for i in range(len(self.stack)):
96 sep = self.stack[i]
Guido van Rossum3ad9dd51998-06-29 17:56:06 +000097 if marker == self.section_divider(sep):
Guido van Rossum741c81a1992-07-13 14:40:45 +000098 self.last = 0
99 break
Guido van Rossum3ad9dd51998-06-29 17:56:06 +0000100 elif marker == self.end_marker(sep):
Guido van Rossum741c81a1992-07-13 14:40:45 +0000101 self.last = 1
102 break
103 else:
104 return line
Guido van Rossum3ad9dd51998-06-29 17:56:06 +0000105 # We only get here if we see a section divider or EOM line
Guido van Rossum4d40b0a1998-06-23 14:20:27 +0000106 if self.seekable:
107 self.lastpos = self.tell() - len(line)
Guido van Rossum741c81a1992-07-13 14:40:45 +0000108 self.level = i+1
109 if self.level > 1:
Guido van Rossum3ad9dd51998-06-29 17:56:06 +0000110 raise Error,'Missing endmarker in MultiFile.readline()'
Guido van Rossum741c81a1992-07-13 14:40:45 +0000111 return ''
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000112
Guido van Rossumb6775db1994-08-01 11:34:53 +0000113 def readlines(self):
114 list = []
115 while 1:
116 line = self.readline()
117 if not line: break
118 list.append(line)
119 return list
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000120
Guido van Rossumb6775db1994-08-01 11:34:53 +0000121 def read(self): # Note: no size argument -- read until EOF only!
122 return string.joinfields(self.readlines(), '')
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000123
Guido van Rossum741c81a1992-07-13 14:40:45 +0000124 def next(self):
125 while self.readline(): pass
126 if self.level > 1 or self.last:
127 return 0
128 self.level = 0
129 self.last = 0
Guido van Rossum4d40b0a1998-06-23 14:20:27 +0000130 if self.seekable:
131 self.start = self.fp.tell()
Guido van Rossum741c81a1992-07-13 14:40:45 +0000132 return 1
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000133
Guido van Rossum741c81a1992-07-13 14:40:45 +0000134 def push(self, sep):
135 if self.level > 0:
136 raise Error, 'bad MultiFile.push() call'
137 self.stack.insert(0, sep)
Guido van Rossum716b7841998-06-29 17:58:43 +0000138 if self.seekable:
139 self.posstack.insert(0, self.start)
140 self.start = self.fp.tell()
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000141
Guido van Rossum741c81a1992-07-13 14:40:45 +0000142 def pop(self):
143 if self.stack == []:
144 raise Error, 'bad MultiFile.pop() call'
145 if self.level <= 1:
146 self.last = 0
147 else:
148 abslastpos = self.lastpos + self.start
149 self.level = max(0, self.level - 1)
150 del self.stack[0]
Guido van Rossum4d40b0a1998-06-23 14:20:27 +0000151 if self.seekable:
152 self.start = self.posstack[0]
153 del self.posstack[0]
154 if self.level > 0:
155 self.lastpos = abslastpos - self.start
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000156
Guido van Rossum3ad9dd51998-06-29 17:56:06 +0000157 def is_data(self, line):
158 return line[:2] <> '--'
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000159
Guido van Rossum3ad9dd51998-06-29 17:56:06 +0000160 def section_divider(self, str):
161 return "--" + str
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000162
Guido van Rossum3ad9dd51998-06-29 17:56:06 +0000163 def end_marker(self, str):
164 return "--" + str + "--"