blob: e82a3fdeafbb64699cfaa4c0dd46782744d9d924 [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
Raymond Hettinger0b569bb2004-05-04 08:21:52 +000041 self.stack = []
Tim Peters07e99cb2001-01-14 23:47:14 +000042 self.level = 0
43 self.last = 0
44 if seekable:
45 self.seekable = 1
46 self.start = self.fp.tell()
Raymond Hettinger0b569bb2004-05-04 08:21:52 +000047 self.posstack = []
Guido van Rossum54f22ed2000-02-04 15:10:34 +000048
Tim Peters07e99cb2001-01-14 23:47:14 +000049 def tell(self):
50 if self.level > 0:
51 return self.lastpos
Martin v. Löwisa6026c62002-09-22 09:01:08 +000052 return self.fp.tell() - self.start
Guido van Rossum54f22ed2000-02-04 15:10:34 +000053
Tim Peters07e99cb2001-01-14 23:47:14 +000054 def seek(self, pos, whence=0):
55 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"
64 if not 0 <= pos <= here or \
65 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
Tim Peters07e99cb2001-01-14 23:47:14 +000071 def readline(self):
72 if self.level > 0:
73 return ''
74 line = self.fp.readline()
75 # Real EOF?
76 if not line:
77 self.level = len(self.stack)
78 self.last = (self.level > 0)
79 if self.last:
80 raise Error, 'sudden EOF in MultiFile.readline()'
81 return ''
82 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
Eric S. Raymonde37340e2001-02-09 16:56:44 +000088 marker = line.rstrip()
Tim Peters07e99cb2001-01-14 23:47:14 +000089 # No? OK, try to match a boundary.
90 # Return the line (unstripped) if we don't.
Raymond Hettinger0b569bb2004-05-04 08:21:52 +000091 for i, sep in enumerate(reversed(self.stack)):
Tim Peters07e99cb2001-01-14 23:47:14 +000092 if marker == self.section_divider(sep):
93 self.last = 0
94 break
95 elif marker == self.end_marker(sep):
96 self.last = 1
97 break
98 else:
99 return line
100 # We only get here if we see a section divider or EOM line
101 if self.seekable:
102 self.lastpos = self.tell() - len(line)
103 self.level = i+1
104 if self.level > 1:
105 raise Error,'Missing endmarker in MultiFile.readline()'
106 return ''
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000107
Tim Peters07e99cb2001-01-14 23:47:14 +0000108 def readlines(self):
109 list = []
110 while 1:
111 line = self.readline()
112 if not line: break
113 list.append(line)
114 return list
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000115
Tim Peters07e99cb2001-01-14 23:47:14 +0000116 def read(self): # Note: no size argument -- read until EOF only!
Fred Drake521c83d2001-03-12 02:56:15 +0000117 return ''.join(self.readlines())
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000118
Tim Peters07e99cb2001-01-14 23:47:14 +0000119 def next(self):
120 while self.readline(): pass
121 if self.level > 1 or self.last:
122 return 0
123 self.level = 0
124 self.last = 0
125 if self.seekable:
126 self.start = self.fp.tell()
127 return 1
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000128
Tim Peters07e99cb2001-01-14 23:47:14 +0000129 def push(self, sep):
130 if self.level > 0:
131 raise Error, 'bad MultiFile.push() call'
Raymond Hettinger0b569bb2004-05-04 08:21:52 +0000132 self.stack.append(sep)
Tim Peters07e99cb2001-01-14 23:47:14 +0000133 if self.seekable:
Raymond Hettinger0b569bb2004-05-04 08:21:52 +0000134 self.posstack.append(self.start)
Tim Peters07e99cb2001-01-14 23:47:14 +0000135 self.start = self.fp.tell()
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000136
Tim Peters07e99cb2001-01-14 23:47:14 +0000137 def pop(self):
138 if self.stack == []:
139 raise Error, 'bad MultiFile.pop() call'
140 if self.level <= 1:
141 self.last = 0
142 else:
143 abslastpos = self.lastpos + self.start
144 self.level = max(0, self.level - 1)
Raymond Hettinger0b569bb2004-05-04 08:21:52 +0000145 self.stack.pop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000146 if self.seekable:
Raymond Hettinger0b569bb2004-05-04 08:21:52 +0000147 self.start = self.posstack.pop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000148 if self.level > 0:
149 self.lastpos = abslastpos - self.start
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000150
Tim Peters07e99cb2001-01-14 23:47:14 +0000151 def is_data(self, line):
152 return line[:2] != '--'
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000153
Tim Peters07e99cb2001-01-14 23:47:14 +0000154 def section_divider(self, str):
155 return "--" + str
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000156
Tim Peters07e99cb2001-01-14 23:47:14 +0000157 def end_marker(self, str):
158 return "--" + str + "--"