Bump version to 1.0.
[python/dscho.git] / Lib / dos-8x3 / multifil.py
blobe43d331c57144a43851eaff6c261672bdc75d441
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 bookkeeping
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 class Error(Exception):
34 pass
36 class MultiFile:
38 seekable = 0
40 def __init__(self, fp, seekable=1):
41 self.fp = fp
42 self.stack = [] # Grows down
43 self.level = 0
44 self.last = 0
45 if seekable:
46 self.seekable = 1
47 self.start = self.fp.tell()
48 self.posstack = [] # Grows down
50 def tell(self):
51 if self.level > 0:
52 return self.lastpos
53 return self.fp.tell() - self.start
55 def seek(self, pos, whence=0):
56 here = self.tell()
57 if whence:
58 if whence == 1:
59 pos = pos + here
60 elif whence == 2:
61 if self.level > 0:
62 pos = pos + self.lastpos
63 else:
64 raise Error, "can't use whence=2 yet"
65 if not 0 <= pos <= here or \
66 self.level > 0 and pos > self.lastpos:
67 raise Error, 'bad MultiFile.seek() call'
68 self.fp.seek(pos + self.start)
69 self.level = 0
70 self.last = 0
72 def readline(self):
73 if self.level > 0:
74 return ''
75 line = self.fp.readline()
76 # Real EOF?
77 if not line:
78 self.level = len(self.stack)
79 self.last = (self.level > 0)
80 if self.last:
81 raise Error, 'sudden EOF in MultiFile.readline()'
82 return ''
83 assert self.level == 0
84 # Fast check to see if this is just data
85 if self.is_data(line):
86 return line
87 else:
88 # Ignore trailing whitespace on marker lines
89 k = len(line) - 1;
90 while line[k] in string.whitespace:
91 k = k - 1
92 marker = line[:k+1]
93 # No? OK, try to match a boundary.
94 # Return the line (unstripped) if we don't.
95 for i in range(len(self.stack)):
96 sep = self.stack[i]
97 if marker == self.section_divider(sep):
98 self.last = 0
99 break
100 elif marker == self.end_marker(sep):
101 self.last = 1
102 break
103 else:
104 return line
105 # We only get here if we see a section divider or EOM line
106 if self.seekable:
107 self.lastpos = self.tell() - len(line)
108 self.level = i+1
109 if self.level > 1:
110 raise Error,'Missing endmarker in MultiFile.readline()'
111 return ''
113 def readlines(self):
114 list = []
115 while 1:
116 line = self.readline()
117 if not line: break
118 list.append(line)
119 return list
121 def read(self): # Note: no size argument -- read until EOF only!
122 return string.joinfields(self.readlines(), '')
124 def next(self):
125 while self.readline(): pass
126 if self.level > 1 or self.last:
127 return 0
128 self.level = 0
129 self.last = 0
130 if self.seekable:
131 self.start = self.fp.tell()
132 return 1
134 def push(self, sep):
135 if self.level > 0:
136 raise Error, 'bad MultiFile.push() call'
137 self.stack.insert(0, sep)
138 if self.seekable:
139 self.posstack.insert(0, self.start)
140 self.start = self.fp.tell()
142 def pop(self):
143 if self.stack == []:
144 raise Error, 'bad MultiFile.pop() call'
145 if self.level <= 1:
146 self.last = 0
147 else:
148 abslastpos = self.lastpos + self.start
149 self.level = max(0, self.level - 1)
150 del self.stack[0]
151 if self.seekable:
152 self.start = self.posstack[0]
153 del self.posstack[0]
154 if self.level > 0:
155 self.lastpos = abslastpos - self.start
157 def is_data(self, line):
158 return line[:2] <> '--'
160 def section_divider(self, str):
161 return "--" + str
163 def end_marker(self, str):
164 return "--" + str + "--"