Ditched '_find_SET()', since it was a no-value-added wrapper around
[python/dscho.git] / Lib / multifile.py
blob977b92f770d318e9020d18913af03783dcd90956
1 """A readline()-style interface to the parts of a multipart message.
3 The MultiFile class makes each part of a multipart message "feel" like
4 an ordinary file, as long as you use fp.readline(). Allows recursive
5 use, for nested multipart messages. Probably best used together
6 with module mimetools.
8 Suggested use:
10 real_fp = open(...)
11 fp = MultiFile(real_fp)
13 "read some lines from fp"
14 fp.push(separator)
15 while 1:
16 "read lines from fp until it returns an empty string" (A)
17 if not fp.next(): break
18 fp.pop()
19 "read remaining lines from fp until it returns an empty string"
21 The latter sequence may be used recursively at (A).
22 It is also allowed to use multiple push()...pop() sequences.
24 If seekable is given as 0, the class code will not do the bookeeping
25 it normally attempts in order to make seeks relative to the beginning of the
26 current file part. This may be useful when using MultiFile with a non-
27 seekable stream object.
28 """
30 import sys
31 import string
33 Error = 'multifile.Error'
35 class MultiFile:
37 seekable = 0
39 def __init__(self, fp, seekable=1):
40 self.fp = fp
41 self.stack = [] # Grows down
42 self.level = 0
43 self.last = 0
44 if seekable:
45 self.seekable = 1
46 self.start = self.fp.tell()
47 self.posstack = [] # Grows down
49 def tell(self):
50 if self.level > 0:
51 return self.lastpos
52 return self.fp.tell() - self.start
54 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
71 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
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.
94 for i in range(len(self.stack)):
95 sep = self.stack[i]
96 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 ''
112 def readlines(self):
113 list = []
114 while 1:
115 line = self.readline()
116 if not line: break
117 list.append(line)
118 return list
120 def read(self): # Note: no size argument -- read until EOF only!
121 return string.joinfields(self.readlines(), '')
123 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
133 def push(self, sep):
134 if self.level > 0:
135 raise Error, 'bad MultiFile.push() call'
136 self.stack.insert(0, sep)
137 if self.seekable:
138 self.posstack.insert(0, self.start)
139 self.start = self.fp.tell()
141 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]
150 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
156 def is_data(self, line):
157 return line[:2] <> '--'
159 def section_divider(self, str):
160 return "--" + str
162 def end_marker(self, str):
163 return "--" + str + "--"