blob: 4dd1b52c54d5ec9048ba156bfbbd41ae037708bb [file] [log] [blame]
Guido van Rossum741c81a1992-07-13 14:40:45 +00001# A class that makes each part of a multipart message "feel" like an
2# ordinary file, as long as you use fp.readline(). Allows recursive
3# use, for nested multipart messages. Probably best used together
4# with module mimetools.
5#
6# Suggested use:
7#
8# real_fp = open(...)
9# fp = MultiFile().init(real_fp)
10#
11# "read some lines from fp"
12# fp.push(separator)
13# while 1:
14# "read lines from fp until it returns an empty string" (A)
15# if not fp.next(): break
16# fp.pop()
17# "read remaining lines from fp until it returns an empty string"
18#
19# The latter sequence may be used recursively at (A).
20# It is also allowed to use multiple push()...pop() sequences.
21# Note that if a nested multipart message is terminated by a separator
22# for an outer message, this is not reported, even though it is really
23# illegal input.
24
25import sys
26import string
27
28err = sys.stderr.write
29
30Error = 'multifile.Error'
31
32class MultiFile:
33 #
34 def init(self, fp):
35 self.fp = fp
36 self.stack = [] # Grows down
37 self.level = 0
38 self.last = 0
39 self.start = self.fp.tell()
40 self.posstack = [] # Grows down
41 return self
42 #
43 def tell(self):
44 if self.level > 0:
45 return self.lastpos
46 return self.fp.tell() - self.start
47 #
48 def seek(self, pos):
49 if not 0 <= pos <= self.tell() or \
50 self.level > 0 and pos > self.lastpos:
51 raise Error, 'bad MultiFile.seek() call'
52 self.fp.seek(pos + self.start)
53 self.level = 0
54 self.last = 0
55 #
56 def readline(self):
57 if self.level > 0: return ''
58 line = self.fp.readline()
59 if not line:
60 self.level = len(self.stack)
61 self.last = (self.level > 0)
62 if self.last:
63 err('*** Sudden EOF in MultiFile.readline()\n')
64 return ''
65 if line[:2] <> '--': return line
66 n = len(line)
67 k = n
68 while k > 0 and line[k-1] in string.whitespace: k = k-1
69 mark = line[2:k]
70 if mark[-2:] == '--': mark1 = mark[:-2]
71 else: mark1 = None
72 for i in range(len(self.stack)):
73 sep = self.stack[i]
74 if sep == mark:
75 self.last = 0
76 break
77 elif mark1 <> None and sep == mark1:
78 self.last = 1
79 break
80 else:
81 return line
82 # Get here after break out of loop
83 self.lastpos = self.tell() - len(line)
84 self.level = i+1
85 if self.level > 1:
86 err('*** Missing endmarker in MultiFile.readline()\n')
87 return ''
88 #
89 def next(self):
90 while self.readline(): pass
91 if self.level > 1 or self.last:
92 return 0
93 self.level = 0
94 self.last = 0
95 self.start = self.fp.tell()
96 return 1
97 #
98 def push(self, sep):
99 if self.level > 0:
100 raise Error, 'bad MultiFile.push() call'
101 self.stack.insert(0, sep)
102 self.posstack.insert(0, self.start)
103 self.start = self.fp.tell()
104 #
105 def pop(self):
106 if self.stack == []:
107 raise Error, 'bad MultiFile.pop() call'
108 if self.level <= 1:
109 self.last = 0
110 else:
111 abslastpos = self.lastpos + self.start
112 self.level = max(0, self.level - 1)
113 del self.stack[0]
114 self.start = self.posstack[0]
115 del self.posstack[0]
116 if self.level > 0:
117 self.lastpos = abslastpos - self.start
118 #