]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/multifile.py
dist/mkfile: run binds in subshell
[plan9front.git] / sys / lib / python / multifile.py
1 """A readline()-style interface to the parts of a multipart message.
2
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.
7
8 Suggested use:
9
10 real_fp = open(...)
11 fp = MultiFile(real_fp)
12
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"
20
21 The latter sequence may be used recursively at (A).
22 It is also allowed to use multiple push()...pop() sequences.
23
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 """
29
30 __all__ = ["MultiFile","Error"]
31
32 class Error(Exception):
33     pass
34
35 class MultiFile:
36
37     seekable = 0
38
39     def __init__(self, fp, seekable=1):
40         self.fp = fp
41         self.stack = []
42         self.level = 0
43         self.last = 0
44         if seekable:
45             self.seekable = 1
46             self.start = self.fp.tell()
47             self.posstack = []
48
49     def tell(self):
50         if self.level > 0:
51             return self.lastpos
52         return self.fp.tell() - self.start
53
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
70
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             marker = line.rstrip()
89         # No?  OK, try to match a boundary.
90         # Return the line (unstripped) if we don't.
91         for i, sep in enumerate(reversed(self.stack)):
92             if marker == self.section_divider(sep):
93                 self.last = 0
94                 break
95             elif marker == self.end_marker(sep):
96                 self.last = 1
97                 break
98         else:
99             return line
100         # We only get here if we see a section divider or EOM line
101         if self.seekable:
102             self.lastpos = self.tell() - len(line)
103         self.level = i+1
104         if self.level > 1:
105             raise Error,'Missing endmarker in MultiFile.readline()'
106         return ''
107
108     def readlines(self):
109         list = []
110         while 1:
111             line = self.readline()
112             if not line: break
113             list.append(line)
114         return list
115
116     def read(self): # Note: no size argument -- read until EOF only!
117         return ''.join(self.readlines())
118
119     def next(self):
120         while self.readline(): pass
121         if self.level > 1 or self.last:
122             return 0
123         self.level = 0
124         self.last = 0
125         if self.seekable:
126             self.start = self.fp.tell()
127         return 1
128
129     def push(self, sep):
130         if self.level > 0:
131             raise Error, 'bad MultiFile.push() call'
132         self.stack.append(sep)
133         if self.seekable:
134             self.posstack.append(self.start)
135             self.start = self.fp.tell()
136
137     def pop(self):
138         if self.stack == []:
139             raise Error, 'bad MultiFile.pop() call'
140         if self.level <= 1:
141             self.last = 0
142         else:
143             abslastpos = self.lastpos + self.start
144         self.level = max(0, self.level - 1)
145         self.stack.pop()
146         if self.seekable:
147             self.start = self.posstack.pop()
148             if self.level > 0:
149                 self.lastpos = abslastpos - self.start
150
151     def is_data(self, line):
152         return line[:2] != '--'
153
154     def section_divider(self, str):
155         return "--" + str
156
157     def end_marker(self, str):
158         return "--" + str + "--"