blob: 35b0200c248adc9988588afa3747582fb4c285e7 [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"""
Brett Cannon43e5ef42008-05-12 03:19:20 +000029from warnings import warn
30warn("the multifile module has been deprecated since Python 2.5",
31 DeprecationWarning, stacklevel=2)
32del warn
Guido van Rossum741c81a1992-07-13 14:40:45 +000033
Skip Montanaro269b83b2001-02-06 01:07:02 +000034__all__ = ["MultiFile","Error"]
35
Fred Drake227b1202000-08-17 05:06:49 +000036class Error(Exception):
Tim Peters07e99cb2001-01-14 23:47:14 +000037 pass
Guido van Rossum741c81a1992-07-13 14:40:45 +000038
39class MultiFile:
Guido van Rossum54f22ed2000-02-04 15:10:34 +000040
Tim Peters07e99cb2001-01-14 23:47:14 +000041 seekable = 0
Guido van Rossum54f22ed2000-02-04 15:10:34 +000042
Tim Peters07e99cb2001-01-14 23:47:14 +000043 def __init__(self, fp, seekable=1):
44 self.fp = fp
Raymond Hettinger0b569bb2004-05-04 08:21:52 +000045 self.stack = []
Tim Peters07e99cb2001-01-14 23:47:14 +000046 self.level = 0
47 self.last = 0
48 if seekable:
49 self.seekable = 1
50 self.start = self.fp.tell()
Raymond Hettinger0b569bb2004-05-04 08:21:52 +000051 self.posstack = []
Guido van Rossum54f22ed2000-02-04 15:10:34 +000052
Tim Peters07e99cb2001-01-14 23:47:14 +000053 def tell(self):
54 if self.level > 0:
55 return self.lastpos
Martin v. Löwisa6026c62002-09-22 09:01:08 +000056 return self.fp.tell() - self.start
Guido van Rossum54f22ed2000-02-04 15:10:34 +000057
Tim Peters07e99cb2001-01-14 23:47:14 +000058 def seek(self, pos, whence=0):
59 here = self.tell()
60 if whence:
61 if whence == 1:
62 pos = pos + here
63 elif whence == 2:
64 if self.level > 0:
65 pos = pos + self.lastpos
66 else:
67 raise Error, "can't use whence=2 yet"
68 if not 0 <= pos <= here or \
69 self.level > 0 and pos > self.lastpos:
70 raise Error, 'bad MultiFile.seek() call'
71 self.fp.seek(pos + self.start)
72 self.level = 0
73 self.last = 0
Guido van Rossum54f22ed2000-02-04 15:10:34 +000074
Tim Peters07e99cb2001-01-14 23:47:14 +000075 def readline(self):
76 if self.level > 0:
77 return ''
78 line = self.fp.readline()
79 # Real EOF?
80 if not line:
81 self.level = len(self.stack)
82 self.last = (self.level > 0)
83 if self.last:
84 raise Error, 'sudden EOF in MultiFile.readline()'
85 return ''
86 assert self.level == 0
87 # Fast check to see if this is just data
88 if self.is_data(line):
89 return line
90 else:
91 # Ignore trailing whitespace on marker lines
Eric S. Raymonde37340e2001-02-09 16:56:44 +000092 marker = line.rstrip()
Tim Peters07e99cb2001-01-14 23:47:14 +000093 # No? OK, try to match a boundary.
94 # Return the line (unstripped) if we don't.
Raymond Hettinger0b569bb2004-05-04 08:21:52 +000095 for i, sep in enumerate(reversed(self.stack)):
Tim Peters07e99cb2001-01-14 23:47:14 +000096 if marker == self.section_divider(sep):
97 self.last = 0
98 break
99 elif marker == self.end_marker(sep):
100 self.last = 1
101 break
102 else:
103 return line
104 # We only get here if we see a section divider or EOM line
105 if self.seekable:
106 self.lastpos = self.tell() - len(line)
107 self.level = i+1
108 if self.level > 1:
109 raise Error,'Missing endmarker in MultiFile.readline()'
110 return ''
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000111
Tim Peters07e99cb2001-01-14 23:47:14 +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
Tim Peters07e99cb2001-01-14 23:47:14 +0000120 def read(self): # Note: no size argument -- read until EOF only!
Fred Drake521c83d2001-03-12 02:56:15 +0000121 return ''.join(self.readlines())
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000122
Tim Peters07e99cb2001-01-14 23:47:14 +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
129 if self.seekable:
130 self.start = self.fp.tell()
131 return 1
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000132
Tim Peters07e99cb2001-01-14 23:47:14 +0000133 def push(self, sep):
134 if self.level > 0:
135 raise Error, 'bad MultiFile.push() call'
Raymond Hettinger0b569bb2004-05-04 08:21:52 +0000136 self.stack.append(sep)
Tim Peters07e99cb2001-01-14 23:47:14 +0000137 if self.seekable:
Raymond Hettinger0b569bb2004-05-04 08:21:52 +0000138 self.posstack.append(self.start)
Tim Peters07e99cb2001-01-14 23:47:14 +0000139 self.start = self.fp.tell()
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000140
Tim Peters07e99cb2001-01-14 23:47:14 +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)
Raymond Hettinger0b569bb2004-05-04 08:21:52 +0000149 self.stack.pop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000150 if self.seekable:
Raymond Hettinger0b569bb2004-05-04 08:21:52 +0000151 self.start = self.posstack.pop()
Tim Peters07e99cb2001-01-14 23:47:14 +0000152 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 + "--"