blob: c3c468a0e6787cbe6d16b1a48f3d4da923f70e79 [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
30import sys
Guido van Rossum741c81a1992-07-13 14:40:45 +000031
Skip Montanaro269b83b2001-02-06 01:07:02 +000032__all__ = ["MultiFile","Error"]
33
Fred Drake227b1202000-08-17 05:06:49 +000034class Error(Exception):
Tim Peters07e99cb2001-01-14 23:47:14 +000035 pass
Guido van Rossum741c81a1992-07-13 14:40:45 +000036
37class MultiFile:
Guido van Rossum54f22ed2000-02-04 15:10:34 +000038
Tim Peters07e99cb2001-01-14 23:47:14 +000039 seekable = 0
Guido van Rossum54f22ed2000-02-04 15:10:34 +000040
Tim Peters07e99cb2001-01-14 23:47:14 +000041 def __init__(self, fp, seekable=1):
42 self.fp = fp
43 self.stack = [] # Grows down
44 self.level = 0
45 self.last = 0
46 if seekable:
47 self.seekable = 1
48 self.start = self.fp.tell()
49 self.posstack = [] # Grows down
Guido van Rossum54f22ed2000-02-04 15:10:34 +000050
Tim Peters07e99cb2001-01-14 23:47:14 +000051 def tell(self):
52 if self.level > 0:
53 return self.lastpos
54 return self.fp.tell() - self.start
Guido van Rossum54f22ed2000-02-04 15:10:34 +000055
Tim Peters07e99cb2001-01-14 23:47:14 +000056 def seek(self, pos, whence=0):
57 here = self.tell()
58 if whence:
59 if whence == 1:
60 pos = pos + here
61 elif whence == 2:
62 if self.level > 0:
63 pos = pos + self.lastpos
64 else:
65 raise Error, "can't use whence=2 yet"
66 if not 0 <= pos <= here or \
67 self.level > 0 and pos > self.lastpos:
68 raise Error, 'bad MultiFile.seek() call'
69 self.fp.seek(pos + self.start)
70 self.level = 0
71 self.last = 0
Guido van Rossum54f22ed2000-02-04 15:10:34 +000072
Tim Peters07e99cb2001-01-14 23:47:14 +000073 def readline(self):
74 if self.level > 0:
75 return ''
76 line = self.fp.readline()
77 # Real EOF?
78 if not line:
79 self.level = len(self.stack)
80 self.last = (self.level > 0)
81 if self.last:
82 raise Error, 'sudden EOF in MultiFile.readline()'
83 return ''
84 assert self.level == 0
85 # Fast check to see if this is just data
86 if self.is_data(line):
87 return line
88 else:
89 # Ignore trailing whitespace on marker lines
Eric S. Raymonde37340e2001-02-09 16:56:44 +000090 marker = line.rstrip()
Tim Peters07e99cb2001-01-14 23:47:14 +000091 # No? OK, try to match a boundary.
92 # Return the line (unstripped) if we don't.
93 for i in range(len(self.stack)):
94 sep = self.stack[i]
95 if marker == self.section_divider(sep):
96 self.last = 0
97 break
98 elif marker == self.end_marker(sep):
99 self.last = 1
100 break
101 else:
102 return line
103 # We only get here if we see a section divider or EOM line
104 if self.seekable:
105 self.lastpos = self.tell() - len(line)
106 self.level = i+1
107 if self.level > 1:
108 raise Error,'Missing endmarker in MultiFile.readline()'
109 return ''
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000110
Tim Peters07e99cb2001-01-14 23:47:14 +0000111 def readlines(self):
112 list = []
113 while 1:
114 line = self.readline()
115 if not line: break
116 list.append(line)
117 return list
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000118
Tim Peters07e99cb2001-01-14 23:47:14 +0000119 def read(self): # Note: no size argument -- read until EOF only!
Fred Drake521c83d2001-03-12 02:56:15 +0000120 return ''.join(self.readlines())
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000121
Tim Peters07e99cb2001-01-14 23:47:14 +0000122 def next(self):
123 while self.readline(): pass
124 if self.level > 1 or self.last:
125 return 0
126 self.level = 0
127 self.last = 0
128 if self.seekable:
129 self.start = self.fp.tell()
130 return 1
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000131
Tim Peters07e99cb2001-01-14 23:47:14 +0000132 def push(self, sep):
133 if self.level > 0:
134 raise Error, 'bad MultiFile.push() call'
135 self.stack.insert(0, sep)
136 if self.seekable:
137 self.posstack.insert(0, self.start)
138 self.start = self.fp.tell()
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000139
Tim Peters07e99cb2001-01-14 23:47:14 +0000140 def pop(self):
141 if self.stack == []:
142 raise Error, 'bad MultiFile.pop() call'
143 if self.level <= 1:
144 self.last = 0
145 else:
146 abslastpos = self.lastpos + self.start
147 self.level = max(0, self.level - 1)
148 del self.stack[0]
149 if self.seekable:
150 self.start = self.posstack[0]
151 del self.posstack[0]
152 if self.level > 0:
153 self.lastpos = abslastpos - self.start
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000154
Tim Peters07e99cb2001-01-14 23:47:14 +0000155 def is_data(self, line):
156 return line[:2] != '--'
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000157
Tim Peters07e99cb2001-01-14 23:47:14 +0000158 def section_divider(self, str):
159 return "--" + str
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000160
Tim Peters07e99cb2001-01-14 23:47:14 +0000161 def end_marker(self, str):
162 return "--" + str + "--"