1
2
3 """
4 Input/Output utility methods and classes.
5 """
6
7 from __future__ import absolute_import
8
9 __docformat__ = "restructuredtext en"
10
11
12
13
14
15 import os
16 import zipfile
17
18
19
20
21
22 __all__ = ['AutoFlush', 'MultiWriter', 'PushbackFile', 'Zip']
23
24
25
26
27
29 """
30 An ``AutoFlush`` wraps a file-like object and flushes the output
31 (via a call to ``flush()`` after every write operation. Here's how
32 to use an ``AutoFlush`` object to force standard output to flush after
33 every write:
34
35 .. python::
36
37 import sys
38 from grizzled.io import AutoFlush
39
40 sys.stdout = AutoFlush(sys.stdout)
41 """
43 """
44 Create a new ``AutoFlush`` object to wrap a file-like object.
45
46 :Parameters:
47 f : file
48 A file-like object that contains both a ``write()`` method
49 and a ``flush()`` method.
50 """
51 self.__file = f
52
54 """
55 Write the specified buffer to the file.
56
57 :Parameters:
58 buf : str or bytes
59 buffer to write
60 """
61 self.__file.write(buf)
62 self.__file.flush()
63
65 """
66 Force a flush.
67 """
68 self.__file.flush()
69
71 """
72 Truncate the underlying file. Might fail.
73
74 :Parameters:
75 size : int
76 Where to truncate. If less than 0, then file's current position
77 is used.
78 """
79 if size < 0:
80 size = self.__file.tell()
81 self.__file.truncate(size)
82
84 """
85 Return the file's current position, if applicable.
86
87 :rtype: int
88 :return: Current file position
89 """
90 return self.__file.tell()
91
92 - def seek(self, offset, whence=os.SEEK_SET):
93 """
94 Set the file's current position. The ``whence`` argument is optional;
95 legal values are:
96
97 - ``os.SEEK_SET`` or 0: absolute file positioning (default)
98 - ``os.SEEK_CUR`` or 1: seek relative to the current position
99 - ``os.SEEK_END`` or 2: seek relative to the file's end
100
101 There is no return value. Note that if the file is opened for
102 appending (mode 'a' or 'a+'), any ``seek()`` operations will be undone
103 at the next write. If the file is only opened for writing in append
104 mode (mode 'a'), this method is essentially a no-op, but it remains
105 useful for files opened in append mode with reading enabled (mode
106 'a+'). If the file is opened in text mode (without 'b'), only offsets
107 returned by ``tell()`` are legal. Use of other offsets causes
108 undefined behavior.
109
110 Note that not all file objects are seekable.
111
112 :Parameters:
113 offset : int
114 where to seek
115 whence : int
116 see above
117 """
118 self.__file.seek(offset, whence)
119
121 """
122 Return the integer file descriptor used by the underlying file.
123
124 :rtype: int
125 :return: the file descriptor
126 """
127 return self.__file.fileno()
128
130 """
131 Wraps multiple file-like objects so that they all may be written at once.
132 For example, the following code arranges to have anything written to
133 ``sys.stdout`` go to ``sys.stdout`` and to a temporary file:
134
135 .. python::
136
137 import sys
138 from grizzled.io import MultiWriter
139
140 sys.stdout = MultiWriter(sys.__stdout__, open('/tmp/log', 'w'))
141 """
143 """
144 Create a new ``MultiWriter`` object to wrap one or more file-like
145 objects.
146
147 :Parameters:
148 args : iterable
149 One or more file-like objects to wrap
150 """
151 self.__files = args
152
154 """
155 Write the specified buffer to the wrapped files.
156
157 :Parameters:
158 buf : str or bytes
159 buffer to write
160 """
161 for f in self.__files:
162 f.write(buf)
163
165 """
166 Force a flush.
167 """
168 for f in self.__files:
169 f.flush()
170
172 """
173 Close all contained files.
174 """
175 for f in self.__files:
176 f.close()
177
179 """
180 A file-like wrapper object that permits pushback.
181 """
183 """
184 Create a new ``PushbackFile`` object to wrap a file-like object.
185
186 :Parameters:
187 f : file
188 A file-like object that contains both a ``write()`` method
189 and a ``flush()`` method.
190 """
191 self.__buf = [c for c in ''.join(f.readlines())]
192
194 """
195 Write the specified buffer to the file. This method throws an
196 unconditional exception, since ``PushbackFile`` objects are read-only.
197
198 :Parameters:
199 buf : str or bytes
200 buffer to write
201
202 :raise NotImplementedError: unconditionally
203 """
204 raise NotImplementedError, 'PushbackFile is read-only'
205
207 """
208 Push a character or string back onto the input stream.
209
210 :Parameters:
211 s : str
212 the string to push back onto the input stream
213 """
214 self.__buf = [c for c in s] + self.__buf
215
216 unread=pushback
217
218 - def read(self, n=-1):
219 """
220 Read *n* bytes from the open file.
221
222 :Parameters:
223 n : int
224 Number of bytes to read. A negative number instructs
225 ``read()`` to read all remaining bytes.
226
227 :return: the bytes read
228 """
229 resultBuf = None
230 if n > len(self.__buf):
231 n = len(self.__buf)
232
233 if (n < 0) or (n >= len(self.__buf)):
234 resultBuf = self.__buf
235 self.__buf = []
236
237 else:
238 resultBuf = self.__buf[0:n]
239 self.__buf = self.__buf[n:]
240
241 return ''.join(resultBuf)
242
244 """
245 Read the next line from the file.
246
247 :Parameters:
248 length : int
249 a length hint, or negative if you don't care
250
251 :rtype: str
252 :return: the line
253 """
254 i = 0
255 while i < len(self.__buf) and (self.__buf[i] != '\n'):
256 i += 1
257
258 result = self.__buf[0:i+1]
259 self.__buf = self.__buf[i+1:]
260 return ''.join(result)
261
263 """
264 Read all remaining lines in the file.
265
266 :rtype: list
267 :return: list of lines
268 """
269 return self.read(-1)
270
273
275 """A file object is its own iterator.
276
277 :rtype: str
278 :return: the next line from the file
279
280 :raise StopIteration: end of file
281 :raise IncludeError: on error
282 """
283 line = self.readline()
284 if (line == None) or (len(line) == 0):
285 raise StopIteration
286 return line
287
289 """Close the file. A no-op in this class."""
290 pass
291
293 """
294 Force a flush. This method throws an unconditional exception, since
295 ``PushbackFile`` objects are read-only.
296
297 :raise NotImplementedError: unconditionally
298 """
299 raise NotImplementedError, 'PushbackFile is read-only'
300
302 """
303 Truncate the underlying file. This method throws an unconditional exception, since
304 ``PushbackFile`` objects are read-only.
305
306 :Parameters:
307 size : int
308 Where to truncate. If less than 0, then file's current
309 position is used
310
311 :raise NotImplementedError: unconditionally
312 """
313 raise NotImplementedError, 'PushbackFile is read-only'
314
316 """
317 Return the file's current position, if applicable. This method throws
318 an unconditional exception, since ``PushbackFile`` objects are
319 read-only.
320
321 :rtype: int
322 :return: Current file position
323
324 :raise NotImplementedError: unconditionally
325 """
326 raise NotImplementedError, 'PushbackFile is not seekable'
327
328 - def seek(self, offset, whence=os.SEEK_SET):
329 """
330 Set the file's current position. This method throws an unconditional
331 exception, since ``PushbackFile`` objects are not seekable.
332
333 :Parameters:
334 offset : int
335 where to seek
336 whence : int
337 see above
338
339 :raise NotImplementedError: unconditionally
340 """
341 raise NotImplementedError, 'PushbackFile is not seekable'
342
344 """
345 Return the integer file descriptor used by the underlying file.
346
347 :rtype: int
348 :return: the file descriptor
349 """
350 return -1
351
352 -class Zip(zipfile.ZipFile):
353 """
354 ``Zip`` extends the standard ``zipfile.ZipFile`` class and provides a
355 method to extract the contents of a zip file into a directory. Adapted
356 from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252508.
357 """
358 - def __init__(self, file, mode="r",
359 compression=zipfile.ZIP_STORED,
360 allow_zip64=False):
361 """
362 Constructor. Initialize a new zip file.
363
364 :Parameters:
365 file : str
366 path to zip file
367 mode : str
368 open mode. Valid values are 'r' (read), 'w' (write), and
369 'a' (append)
370 compression : int
371 Compression type. Valid values: ``zipfile.ZIP_STORED`,
372 ``zipfile.ZIP_DEFLATED``
373 allow_zip64 : bool
374 Whether or not Zip64 extensions are to be used
375 """
376 zipfile.ZipFile.__init__(self, file, mode, compression, allow_zip64)
377 self.zipFile = file
378
380 """
381 Unpack the zip file into the specified output directory.
382
383 :Parameters:
384 output_dir : str
385 path to output directory. The directory is
386 created if it doesn't already exist.
387 """
388 if not output_dir.endswith(':') and not os.path.exists(output_dir):
389 os.mkdir(output_dir)
390
391 num_files = len(self.namelist())
392
393
394 for i, name in enumerate(self.namelist()):
395 if not name.endswith('/'):
396 directory = os.path.dirname(name)
397 if directory == '':
398 directory = None
399 if directory:
400 directory = os.path.join(output_dir, directory)
401 if not os.path.exists(directory):
402 os.makedirs(directory)
403
404 outfile = open(os.path.join(output_dir, name), 'wb')
405 outfile.write(self.read(name))
406 outfile.flush()
407 outfile.close()
408