blob: cc8f43c25b8dcf0df4d3ef74ac53025acc0cb908 [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
Guido van Rossum741c81a1992-07-13 14:40:45 +000033Error = 'multifile.Error'
34
35class MultiFile:
Guido van Rossum54f22ed2000-02-04 15:10:34 +000036
Guido van Rossum4d40b0a1998-06-23 14:20:27 +000037 seekable = 0
Guido van Rossum54f22ed2000-02-04 15:10:34 +000038
Guido van Rossum4d40b0a1998-06-23 14:20:27 +000039 def __init__(self, fp, seekable=1):
Guido van Rossum741c81a1992-07-13 14:40:45 +000040 self.fp = fp
41 self.stack = [] # Grows down
42 self.level = 0
43 self.last = 0
Guido van Rossum4d40b0a1998-06-23 14:20:27 +000044 if seekable:
45 self.seekable = 1
46 self.start = self.fp.tell()
47 self.posstack = [] # Grows down
Guido van Rossum54f22ed2000-02-04 15:10:34 +000048
Guido van Rossum741c81a1992-07-13 14:40:45 +000049 def tell(self):
50 if self.level > 0:
51 return self.lastpos
52 return self.fp.tell() - self.start
Guido van Rossum54f22ed2000-02-04 15:10:34 +000053
Guido van Rossum69c70a21998-03-25 16:25:26 +000054 def seek(self, pos, whence=0):
Guido van Rossum8ca84201998-03-26 20:56:10 +000055 here = self.tell()
56 if whence:
57 if whence == 1:
58 pos = pos + here
59 elif whence == 2:
60 if self.level > 0:
61 pos = pos + self.lastpos
62 else:
63 raise Error, "can't use whence=2 yet"
Guido van Rossum69c70a21998-03-25 16:25:26 +000064 if not 0 <= pos <= here or \
Guido van Rossum741c81a1992-07-13 14:40:45 +000065 self.level > 0 and pos > self.lastpos:
66 raise Error, 'bad MultiFile.seek() call'
67 self.fp.seek(pos + self.start)
68 self.level = 0
69 self.last = 0
Guido van Rossum54f22ed2000-02-04 15:10:34 +000070
Guido van Rossum741c81a1992-07-13 14:40:45 +000071 def readline(self):
Guido van Rossum3ad9dd51998-06-29 17:56:06 +000072 if self.level > 0:
73 return ''
Guido van Rossum741c81a1992-07-13 14:40:45 +000074 line = self.fp.readline()
Guido van Rossum3ad9dd51998-06-29 17:56:06 +000075 # Real EOF?
Guido van Rossum741c81a1992-07-13 14:40:45 +000076 if not line:
77 self.level = len(self.stack)
78 self.last = (self.level > 0)
79 if self.last:
Guido van Rossum3ad9dd51998-06-29 17:56:06 +000080 raise Error, 'sudden EOF in MultiFile.readline()'
Guido van Rossum741c81a1992-07-13 14:40:45 +000081 return ''
Guido van Rossum3ad9dd51998-06-29 17:56:06 +000082 assert self.level == 0
83 # Fast check to see if this is just data
84 if self.is_data(line):
85 return line
86 else:
87 # Ignore trailing whitespace on marker lines
88 k = len(line) - 1;
89 while line[k] in string.whitespace:
90 k = k - 1
91 marker = line[:k+1]
92 # No? OK, try to match a boundary.
93 # Return the line (unstripped) if we don't.
Guido van Rossum741c81a1992-07-13 14:40:45 +000094 for i in range(len(self.stack)):
95 sep = self.stack[i]
Guido van Rossum3ad9dd51998-06-29 17:56:06 +000096 if marker == self.section_divider(sep):
Guido van Rossum741c81a1992-07-13 14:40:45 +000097 self.last = 0
98 break
Guido van Rossum3ad9dd51998-06-29 17:56:06 +000099 elif marker == self.end_marker(sep):
Guido van Rossum741c81a1992-07-13 14:40:45 +0000100 self.last = 1
101 break
102 else:
103 return line
Guido van Rossum3ad9dd51998-06-29 17:56:06 +0000104 # We only get here if we see a section divider or EOM line
Guido van Rossum4d40b0a1998-06-23 14:20:27 +0000105 if self.seekable:
106 self.lastpos = self.tell() - len(line)
Guido van Rossum741c81a1992-07-13 14:40:45 +0000107 self.level = i+1
108 if self.level > 1:
Guido van Rossum3ad9dd51998-06-29 17:56:06 +0000109 raise Error,'Missing endmarker in MultiFile.readline()'
Guido van Rossum741c81a1992-07-13 14:40:45 +0000110 return ''
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000111
Guido van Rossumb6775db1994-08-01 11:34:53 +0000112 def readlines(self):
113 list = []
114 while 1:
115 line = self.readline()
116 if not line: break
117 list.append(line)
118 return list
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000119
Guido van Rossumb6775db1994-08-01 11:34:53 +0000120 def read(self): # Note: no size argument -- read until EOF only!
121 return string.joinfields(self.readlines(), '')
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000122
Guido van Rossum741c81a1992-07-13 14:40:45 +0000123 def next(self):
124 while self.readline(): pass
125 if self.level > 1 or self.last:
126 return 0
127 self.level = 0
128 self.last = 0
Guido van Rossum4d40b0a1998-06-23 14:20:27 +0000129 if self.seekable:
130 self.start = self.fp.tell()
Guido van Rossum741c81a1992-07-13 14:40:45 +0000131 return 1
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000132
Guido van Rossum741c81a1992-07-13 14:40:45 +0000133 def push(self, sep):
134 if self.level > 0:
135 raise Error, 'bad MultiFile.push() call'
136 self.stack.insert(0, sep)
Guido van Rossum716b7841998-06-29 17:58:43 +0000137 if self.seekable:
138 self.posstack.insert(0, self.start)
139 self.start = self.fp.tell()
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000140
Guido van Rossum741c81a1992-07-13 14:40:45 +0000141 def pop(self):
142 if self.stack == []:
143 raise Error, 'bad MultiFile.pop() call'
144 if self.level <= 1:
145 self.last = 0
146 else:
147 abslastpos = self.lastpos + self.start
148 self.level = max(0, self.level - 1)
149 del self.stack[0]
Guido van Rossum4d40b0a1998-06-23 14:20:27 +0000150 if self.seekable:
151 self.start = self.posstack[0]
152 del self.posstack[0]
153 if self.level > 0:
154 self.lastpos = abslastpos - self.start
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000155
Guido van Rossum3ad9dd51998-06-29 17:56:06 +0000156 def is_data(self, line):
157 return line[:2] <> '--'
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000158
Guido van Rossum3ad9dd51998-06-29 17:56:06 +0000159 def section_divider(self, str):
160 return "--" + str
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000161
Guido van Rossum3ad9dd51998-06-29 17:56:06 +0000162 def end_marker(self, str):
163 return "--" + str + "--"