blob: e0305d82b2ac112f9dab0bec0967d4937434e30c [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
31import string
32
Skip Montanaro269b83b2001-02-06 01:07:02 +000033__all__ = ["MultiFile","Error"]
34
Fred Drake227b1202000-08-17 05:06:49 +000035class Error(Exception):
Tim Peters07e99cb2001-01-14 23:47:14 +000036 pass
Guido van Rossum741c81a1992-07-13 14:40:45 +000037
38class MultiFile:
Guido van Rossum54f22ed2000-02-04 15:10:34 +000039
Tim Peters07e99cb2001-01-14 23:47:14 +000040 seekable = 0
Guido van Rossum54f22ed2000-02-04 15:10:34 +000041
Tim Peters07e99cb2001-01-14 23:47:14 +000042 def __init__(self, fp, seekable=1):
43 self.fp = fp
44 self.stack = [] # Grows down
45 self.level = 0
46 self.last = 0
47 if seekable:
48 self.seekable = 1
49 self.start = self.fp.tell()
50 self.posstack = [] # Grows down
Guido van Rossum54f22ed2000-02-04 15:10:34 +000051
Tim Peters07e99cb2001-01-14 23:47:14 +000052 def tell(self):
53 if self.level > 0:
54 return self.lastpos
55 return self.fp.tell() - self.start
Guido van Rossum54f22ed2000-02-04 15:10:34 +000056
Tim Peters07e99cb2001-01-14 23:47:14 +000057 def seek(self, pos, whence=0):
58 here = self.tell()
59 if whence:
60 if whence == 1:
61 pos = pos + here
62 elif whence == 2:
63 if self.level > 0:
64 pos = pos + self.lastpos
65 else:
66 raise Error, "can't use whence=2 yet"
67 if not 0 <= pos <= here or \
68 self.level > 0 and pos > self.lastpos:
69 raise Error, 'bad MultiFile.seek() call'
70 self.fp.seek(pos + self.start)
71 self.level = 0
72 self.last = 0
Guido van Rossum54f22ed2000-02-04 15:10:34 +000073
Tim Peters07e99cb2001-01-14 23:47:14 +000074 def readline(self):
75 if self.level > 0:
76 return ''
77 line = self.fp.readline()
78 # Real EOF?
79 if not line:
80 self.level = len(self.stack)
81 self.last = (self.level > 0)
82 if self.last:
83 raise Error, 'sudden EOF in MultiFile.readline()'
84 return ''
85 assert self.level == 0
86 # Fast check to see if this is just data
87 if self.is_data(line):
88 return line
89 else:
90 # Ignore trailing whitespace on marker lines
91 k = len(line) - 1
92 while line[k] in string.whitespace:
93 k = k - 1
94 marker = line[:k+1]
95 # No? OK, try to match a boundary.
96 # Return the line (unstripped) if we don't.
97 for i in range(len(self.stack)):
98 sep = self.stack[i]
99 if marker == self.section_divider(sep):
100 self.last = 0
101 break
102 elif marker == self.end_marker(sep):
103 self.last = 1
104 break
105 else:
106 return line
107 # We only get here if we see a section divider or EOM line
108 if self.seekable:
109 self.lastpos = self.tell() - len(line)
110 self.level = i+1
111 if self.level > 1:
112 raise Error,'Missing endmarker in MultiFile.readline()'
113 return ''
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000114
Tim Peters07e99cb2001-01-14 23:47:14 +0000115 def readlines(self):
116 list = []
117 while 1:
118 line = self.readline()
119 if not line: break
120 list.append(line)
121 return list
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000122
Tim Peters07e99cb2001-01-14 23:47:14 +0000123 def read(self): # Note: no size argument -- read until EOF only!
124 return string.joinfields(self.readlines(), '')
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000125
Tim Peters07e99cb2001-01-14 23:47:14 +0000126 def next(self):
127 while self.readline(): pass
128 if self.level > 1 or self.last:
129 return 0
130 self.level = 0
131 self.last = 0
132 if self.seekable:
133 self.start = self.fp.tell()
134 return 1
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000135
Tim Peters07e99cb2001-01-14 23:47:14 +0000136 def push(self, sep):
137 if self.level > 0:
138 raise Error, 'bad MultiFile.push() call'
139 self.stack.insert(0, sep)
140 if self.seekable:
141 self.posstack.insert(0, self.start)
142 self.start = self.fp.tell()
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000143
Tim Peters07e99cb2001-01-14 23:47:14 +0000144 def pop(self):
145 if self.stack == []:
146 raise Error, 'bad MultiFile.pop() call'
147 if self.level <= 1:
148 self.last = 0
149 else:
150 abslastpos = self.lastpos + self.start
151 self.level = max(0, self.level - 1)
152 del self.stack[0]
153 if self.seekable:
154 self.start = self.posstack[0]
155 del self.posstack[0]
156 if self.level > 0:
157 self.lastpos = abslastpos - self.start
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000158
Tim Peters07e99cb2001-01-14 23:47:14 +0000159 def is_data(self, line):
160 return line[:2] != '--'
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000161
Tim Peters07e99cb2001-01-14 23:47:14 +0000162 def section_divider(self, str):
163 return "--" + str
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000164
Tim Peters07e99cb2001-01-14 23:47:14 +0000165 def end_marker(self, str):
166 return "--" + str + "--"