##// END OF EJS Templates
Bundled path.py should not modify the os module
Thomas Kluyver -
Show More
@@ -1,1267 +1,1267 b''
1 #
1 #
2 # Copyright (c) 2010 Mikhail Gusarov
2 # Copyright (c) 2010 Mikhail Gusarov
3 #
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining a copy
4 # Permission is hereby granted, free of charge, to any person obtaining a copy
5 # of this software and associated documentation files (the "Software"), to deal
5 # of this software and associated documentation files (the "Software"), to deal
6 # in the Software without restriction, including without limitation the rights
6 # in the Software without restriction, including without limitation the rights
7 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # copies of the Software, and to permit persons to whom the Software is
8 # copies of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
9 # furnished to do so, subject to the following conditions:
10 #
10 #
11 # The above copyright notice and this permission notice shall be included in
11 # The above copyright notice and this permission notice shall be included in
12 # all copies or substantial portions of the Software.
12 # all copies or substantial portions of the Software.
13 #
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 # SOFTWARE.
20 # SOFTWARE.
21 #
21 #
22
22
23 """ path.py - An object representing a path to a file or directory.
23 """ path.py - An object representing a path to a file or directory.
24
24
25 Original author:
25 Original author:
26 Jason Orendorff <jason.orendorff\x40gmail\x2ecom>
26 Jason Orendorff <jason.orendorff\x40gmail\x2ecom>
27
27
28 Current maintainer:
28 Current maintainer:
29 Jason R. Coombs <jaraco@jaraco.com>
29 Jason R. Coombs <jaraco@jaraco.com>
30
30
31 Contributors:
31 Contributors:
32 Mikhail Gusarov <dottedmag@dottedmag.net>
32 Mikhail Gusarov <dottedmag@dottedmag.net>
33 Marc Abramowitz <marc@marc-abramowitz.com>
33 Marc Abramowitz <marc@marc-abramowitz.com>
34 Jason R. Coombs <jaraco@jaraco.com>
34 Jason R. Coombs <jaraco@jaraco.com>
35 Jason Chu <jchu@xentac.net>
35 Jason Chu <jchu@xentac.net>
36 Vojislav Stojkovic <vstojkovic@syntertainment.com>
36 Vojislav Stojkovic <vstojkovic@syntertainment.com>
37
37
38 Example::
38 Example::
39
39
40 from path import path
40 from path import path
41 d = path('/home/guido/bin')
41 d = path('/home/guido/bin')
42 for f in d.files('*.py'):
42 for f in d.files('*.py'):
43 f.chmod(0755)
43 f.chmod(0755)
44
44
45 path.py requires Python 2.5 or later.
45 path.py requires Python 2.5 or later.
46 """
46 """
47
47
48 from __future__ import with_statement
48 from __future__ import with_statement
49
49
50 import sys
50 import sys
51 import warnings
51 import warnings
52 import os
52 import os
53 import fnmatch
53 import fnmatch
54 import glob
54 import glob
55 import shutil
55 import shutil
56 import codecs
56 import codecs
57 import hashlib
57 import hashlib
58 import errno
58 import errno
59 import tempfile
59 import tempfile
60 import functools
60 import functools
61 import operator
61 import operator
62 import re
62 import re
63
63
64 try:
64 try:
65 import win32security
65 import win32security
66 except ImportError:
66 except ImportError:
67 pass
67 pass
68
68
69 try:
69 try:
70 import pwd
70 import pwd
71 except ImportError:
71 except ImportError:
72 pass
72 pass
73
73
74 ################################
74 ################################
75 # Monkey patchy python 3 support
75 # Monkey patchy python 3 support
76 try:
76 try:
77 basestring
77 basestring
78 except NameError:
78 except NameError:
79 basestring = str
79 basestring = str
80
80
81 try:
81 try:
82 unicode
82 unicode
83 except NameError:
83 except NameError:
84 unicode = str
84 unicode = str
85
85
86 try:
86 try:
87 os.getcwdu
87 getcwdu = os.getcwdu
88 except AttributeError:
88 except AttributeError:
89 os.getcwdu = os.getcwd
89 getcwdu = os.getcwd
90
90
91 if sys.version < '3':
91 if sys.version < '3':
92 def u(x):
92 def u(x):
93 return codecs.unicode_escape_decode(x)[0]
93 return codecs.unicode_escape_decode(x)[0]
94 else:
94 else:
95 def u(x):
95 def u(x):
96 return x
96 return x
97
97
98 o777 = 511
98 o777 = 511
99 o766 = 502
99 o766 = 502
100 o666 = 438
100 o666 = 438
101 o554 = 364
101 o554 = 364
102 ################################
102 ################################
103
103
104 __version__ = '4.3'
104 __version__ = '4.3'
105 __all__ = ['path']
105 __all__ = ['path']
106
106
107
107
108 class TreeWalkWarning(Warning):
108 class TreeWalkWarning(Warning):
109 pass
109 pass
110
110
111
111
112 def simple_cache(func):
112 def simple_cache(func):
113 """
113 """
114 Save results for the 'using_module' classmethod.
114 Save results for the 'using_module' classmethod.
115 When Python 3.2 is available, use functools.lru_cache instead.
115 When Python 3.2 is available, use functools.lru_cache instead.
116 """
116 """
117 saved_results = {}
117 saved_results = {}
118
118
119 def wrapper(cls, module):
119 def wrapper(cls, module):
120 if module in saved_results:
120 if module in saved_results:
121 return saved_results[module]
121 return saved_results[module]
122 saved_results[module] = func(cls, module)
122 saved_results[module] = func(cls, module)
123 return saved_results[module]
123 return saved_results[module]
124 return wrapper
124 return wrapper
125
125
126
126
127 class ClassProperty(property):
127 class ClassProperty(property):
128 def __get__(self, cls, owner):
128 def __get__(self, cls, owner):
129 return self.fget.__get__(None, owner)()
129 return self.fget.__get__(None, owner)()
130
130
131
131
132 class multimethod(object):
132 class multimethod(object):
133 """
133 """
134 Acts like a classmethod when invoked from the class and like an
134 Acts like a classmethod when invoked from the class and like an
135 instancemethod when invoked from the instance.
135 instancemethod when invoked from the instance.
136 """
136 """
137 def __init__(self, func):
137 def __init__(self, func):
138 self.func = func
138 self.func = func
139
139
140 def __get__(self, instance, owner):
140 def __get__(self, instance, owner):
141 return (
141 return (
142 functools.partial(self.func, owner) if instance is None
142 functools.partial(self.func, owner) if instance is None
143 else functools.partial(self.func, owner, instance)
143 else functools.partial(self.func, owner, instance)
144 )
144 )
145
145
146
146
147 class path(unicode):
147 class path(unicode):
148 """ Represents a filesystem path.
148 """ Represents a filesystem path.
149
149
150 For documentation on individual methods, consult their
150 For documentation on individual methods, consult their
151 counterparts in os.path.
151 counterparts in os.path.
152 """
152 """
153
153
154 module = os.path
154 module = os.path
155 "The path module to use for path operations."
155 "The path module to use for path operations."
156
156
157 def __init__(self, other=''):
157 def __init__(self, other=''):
158 if other is None:
158 if other is None:
159 raise TypeError("Invalid initial value for path: None")
159 raise TypeError("Invalid initial value for path: None")
160
160
161 @classmethod
161 @classmethod
162 @simple_cache
162 @simple_cache
163 def using_module(cls, module):
163 def using_module(cls, module):
164 subclass_name = cls.__name__ + '_' + module.__name__
164 subclass_name = cls.__name__ + '_' + module.__name__
165 bases = (cls,)
165 bases = (cls,)
166 ns = {'module': module}
166 ns = {'module': module}
167 return type(subclass_name, bases, ns)
167 return type(subclass_name, bases, ns)
168
168
169 @ClassProperty
169 @ClassProperty
170 @classmethod
170 @classmethod
171 def _next_class(cls):
171 def _next_class(cls):
172 """
172 """
173 What class should be used to construct new instances from this class
173 What class should be used to construct new instances from this class
174 """
174 """
175 return cls
175 return cls
176
176
177 # --- Special Python methods.
177 # --- Special Python methods.
178
178
179 def __repr__(self):
179 def __repr__(self):
180 return '%s(%s)' % (type(self).__name__, super(path, self).__repr__())
180 return '%s(%s)' % (type(self).__name__, super(path, self).__repr__())
181
181
182 # Adding a path and a string yields a path.
182 # Adding a path and a string yields a path.
183 def __add__(self, more):
183 def __add__(self, more):
184 try:
184 try:
185 return self._next_class(super(path, self).__add__(more))
185 return self._next_class(super(path, self).__add__(more))
186 except TypeError: # Python bug
186 except TypeError: # Python bug
187 return NotImplemented
187 return NotImplemented
188
188
189 def __radd__(self, other):
189 def __radd__(self, other):
190 if not isinstance(other, basestring):
190 if not isinstance(other, basestring):
191 return NotImplemented
191 return NotImplemented
192 return self._next_class(other.__add__(self))
192 return self._next_class(other.__add__(self))
193
193
194 # The / operator joins paths.
194 # The / operator joins paths.
195 def __div__(self, rel):
195 def __div__(self, rel):
196 """ fp.__div__(rel) == fp / rel == fp.joinpath(rel)
196 """ fp.__div__(rel) == fp / rel == fp.joinpath(rel)
197
197
198 Join two path components, adding a separator character if
198 Join two path components, adding a separator character if
199 needed.
199 needed.
200 """
200 """
201 return self._next_class(self.module.join(self, rel))
201 return self._next_class(self.module.join(self, rel))
202
202
203 # Make the / operator work even when true division is enabled.
203 # Make the / operator work even when true division is enabled.
204 __truediv__ = __div__
204 __truediv__ = __div__
205
205
206 def __enter__(self):
206 def __enter__(self):
207 self._old_dir = self.getcwd()
207 self._old_dir = self.getcwd()
208 os.chdir(self)
208 os.chdir(self)
209 return self
209 return self
210
210
211 def __exit__(self, *_):
211 def __exit__(self, *_):
212 os.chdir(self._old_dir)
212 os.chdir(self._old_dir)
213
213
214 @classmethod
214 @classmethod
215 def getcwd(cls):
215 def getcwd(cls):
216 """ Return the current working directory as a path object. """
216 """ Return the current working directory as a path object. """
217 return cls(os.getcwdu())
217 return cls(getcwdu())
218
218
219 #
219 #
220 # --- Operations on path strings.
220 # --- Operations on path strings.
221
221
222 def abspath(self):
222 def abspath(self):
223 return self._next_class(self.module.abspath(self))
223 return self._next_class(self.module.abspath(self))
224
224
225 def normcase(self):
225 def normcase(self):
226 return self._next_class(self.module.normcase(self))
226 return self._next_class(self.module.normcase(self))
227
227
228 def normpath(self):
228 def normpath(self):
229 return self._next_class(self.module.normpath(self))
229 return self._next_class(self.module.normpath(self))
230
230
231 def realpath(self):
231 def realpath(self):
232 return self._next_class(self.module.realpath(self))
232 return self._next_class(self.module.realpath(self))
233
233
234 def expanduser(self):
234 def expanduser(self):
235 return self._next_class(self.module.expanduser(self))
235 return self._next_class(self.module.expanduser(self))
236
236
237 def expandvars(self):
237 def expandvars(self):
238 return self._next_class(self.module.expandvars(self))
238 return self._next_class(self.module.expandvars(self))
239
239
240 def dirname(self):
240 def dirname(self):
241 return self._next_class(self.module.dirname(self))
241 return self._next_class(self.module.dirname(self))
242
242
243 def basename(self):
243 def basename(self):
244 return self._next_class(self.module.basename(self))
244 return self._next_class(self.module.basename(self))
245
245
246 def expand(self):
246 def expand(self):
247 """ Clean up a filename by calling expandvars(),
247 """ Clean up a filename by calling expandvars(),
248 expanduser(), and normpath() on it.
248 expanduser(), and normpath() on it.
249
249
250 This is commonly everything needed to clean up a filename
250 This is commonly everything needed to clean up a filename
251 read from a configuration file, for example.
251 read from a configuration file, for example.
252 """
252 """
253 return self.expandvars().expanduser().normpath()
253 return self.expandvars().expanduser().normpath()
254
254
255 @property
255 @property
256 def namebase(self):
256 def namebase(self):
257 """ The same as path.name, but with one file extension stripped off.
257 """ The same as path.name, but with one file extension stripped off.
258
258
259 For example, path('/home/guido/python.tar.gz').name == 'python.tar.gz',
259 For example, path('/home/guido/python.tar.gz').name == 'python.tar.gz',
260 but path('/home/guido/python.tar.gz').namebase == 'python.tar'
260 but path('/home/guido/python.tar.gz').namebase == 'python.tar'
261 """
261 """
262 base, ext = self.module.splitext(self.name)
262 base, ext = self.module.splitext(self.name)
263 return base
263 return base
264
264
265 @property
265 @property
266 def ext(self):
266 def ext(self):
267 """ The file extension, for example '.py'. """
267 """ The file extension, for example '.py'. """
268 f, ext = self.module.splitext(self)
268 f, ext = self.module.splitext(self)
269 return ext
269 return ext
270
270
271 @property
271 @property
272 def drive(self):
272 def drive(self):
273 """ The drive specifier, for example 'C:'.
273 """ The drive specifier, for example 'C:'.
274 This is always empty on systems that don't use drive specifiers.
274 This is always empty on systems that don't use drive specifiers.
275 """
275 """
276 drive, r = self.module.splitdrive(self)
276 drive, r = self.module.splitdrive(self)
277 return self._next_class(drive)
277 return self._next_class(drive)
278
278
279 parent = property(
279 parent = property(
280 dirname, None, None,
280 dirname, None, None,
281 """ This path's parent directory, as a new path object.
281 """ This path's parent directory, as a new path object.
282
282
283 For example,
283 For example,
284 path('/usr/local/lib/libpython.so').parent == path('/usr/local/lib')
284 path('/usr/local/lib/libpython.so').parent == path('/usr/local/lib')
285 """)
285 """)
286
286
287 name = property(
287 name = property(
288 basename, None, None,
288 basename, None, None,
289 """ The name of this file or directory without the full path.
289 """ The name of this file or directory without the full path.
290
290
291 For example, path('/usr/local/lib/libpython.so').name == 'libpython.so'
291 For example, path('/usr/local/lib/libpython.so').name == 'libpython.so'
292 """)
292 """)
293
293
294 def splitpath(self):
294 def splitpath(self):
295 """ p.splitpath() -> Return (p.parent, p.name). """
295 """ p.splitpath() -> Return (p.parent, p.name). """
296 parent, child = self.module.split(self)
296 parent, child = self.module.split(self)
297 return self._next_class(parent), child
297 return self._next_class(parent), child
298
298
299 def splitdrive(self):
299 def splitdrive(self):
300 """ p.splitdrive() -> Return (p.drive, <the rest of p>).
300 """ p.splitdrive() -> Return (p.drive, <the rest of p>).
301
301
302 Split the drive specifier from this path. If there is
302 Split the drive specifier from this path. If there is
303 no drive specifier, p.drive is empty, so the return value
303 no drive specifier, p.drive is empty, so the return value
304 is simply (path(''), p). This is always the case on Unix.
304 is simply (path(''), p). This is always the case on Unix.
305 """
305 """
306 drive, rel = self.module.splitdrive(self)
306 drive, rel = self.module.splitdrive(self)
307 return self._next_class(drive), rel
307 return self._next_class(drive), rel
308
308
309 def splitext(self):
309 def splitext(self):
310 """ p.splitext() -> Return (p.stripext(), p.ext).
310 """ p.splitext() -> Return (p.stripext(), p.ext).
311
311
312 Split the filename extension from this path and return
312 Split the filename extension from this path and return
313 the two parts. Either part may be empty.
313 the two parts. Either part may be empty.
314
314
315 The extension is everything from '.' to the end of the
315 The extension is everything from '.' to the end of the
316 last path segment. This has the property that if
316 last path segment. This has the property that if
317 (a, b) == p.splitext(), then a + b == p.
317 (a, b) == p.splitext(), then a + b == p.
318 """
318 """
319 filename, ext = self.module.splitext(self)
319 filename, ext = self.module.splitext(self)
320 return self._next_class(filename), ext
320 return self._next_class(filename), ext
321
321
322 def stripext(self):
322 def stripext(self):
323 """ p.stripext() -> Remove one file extension from the path.
323 """ p.stripext() -> Remove one file extension from the path.
324
324
325 For example, path('/home/guido/python.tar.gz').stripext()
325 For example, path('/home/guido/python.tar.gz').stripext()
326 returns path('/home/guido/python.tar').
326 returns path('/home/guido/python.tar').
327 """
327 """
328 return self.splitext()[0]
328 return self.splitext()[0]
329
329
330 def splitunc(self):
330 def splitunc(self):
331 unc, rest = self.module.splitunc(self)
331 unc, rest = self.module.splitunc(self)
332 return self._next_class(unc), rest
332 return self._next_class(unc), rest
333
333
334 @property
334 @property
335 def uncshare(self):
335 def uncshare(self):
336 """
336 """
337 The UNC mount point for this path.
337 The UNC mount point for this path.
338 This is empty for paths on local drives.
338 This is empty for paths on local drives.
339 """
339 """
340 unc, r = self.module.splitunc(self)
340 unc, r = self.module.splitunc(self)
341 return self._next_class(unc)
341 return self._next_class(unc)
342
342
343 @multimethod
343 @multimethod
344 def joinpath(cls, first, *others):
344 def joinpath(cls, first, *others):
345 """
345 """
346 Join first to zero or more path components, adding a separator
346 Join first to zero or more path components, adding a separator
347 character (first.module.sep) if needed. Returns a new instance of
347 character (first.module.sep) if needed. Returns a new instance of
348 first._next_class.
348 first._next_class.
349 """
349 """
350 if not isinstance(first, cls):
350 if not isinstance(first, cls):
351 first = cls(first)
351 first = cls(first)
352 return first._next_class(first.module.join(first, *others))
352 return first._next_class(first.module.join(first, *others))
353
353
354 def splitall(self):
354 def splitall(self):
355 r""" Return a list of the path components in this path.
355 r""" Return a list of the path components in this path.
356
356
357 The first item in the list will be a path. Its value will be
357 The first item in the list will be a path. Its value will be
358 either os.curdir, os.pardir, empty, or the root directory of
358 either os.curdir, os.pardir, empty, or the root directory of
359 this path (for example, ``'/'`` or ``'C:\\'``). The other items in
359 this path (for example, ``'/'`` or ``'C:\\'``). The other items in
360 the list will be strings.
360 the list will be strings.
361
361
362 ``path.path.joinpath(*result)`` will yield the original path.
362 ``path.path.joinpath(*result)`` will yield the original path.
363 """
363 """
364 parts = []
364 parts = []
365 loc = self
365 loc = self
366 while loc != os.curdir and loc != os.pardir:
366 while loc != os.curdir and loc != os.pardir:
367 prev = loc
367 prev = loc
368 loc, child = prev.splitpath()
368 loc, child = prev.splitpath()
369 if loc == prev:
369 if loc == prev:
370 break
370 break
371 parts.append(child)
371 parts.append(child)
372 parts.append(loc)
372 parts.append(loc)
373 parts.reverse()
373 parts.reverse()
374 return parts
374 return parts
375
375
376 def relpath(self, start='.'):
376 def relpath(self, start='.'):
377 """ Return this path as a relative path,
377 """ Return this path as a relative path,
378 based from start, which defaults to the current working directory.
378 based from start, which defaults to the current working directory.
379 """
379 """
380 cwd = self._next_class(start)
380 cwd = self._next_class(start)
381 return cwd.relpathto(self)
381 return cwd.relpathto(self)
382
382
383 def relpathto(self, dest):
383 def relpathto(self, dest):
384 """ Return a relative path from self to dest.
384 """ Return a relative path from self to dest.
385
385
386 If there is no relative path from self to dest, for example if
386 If there is no relative path from self to dest, for example if
387 they reside on different drives in Windows, then this returns
387 they reside on different drives in Windows, then this returns
388 dest.abspath().
388 dest.abspath().
389 """
389 """
390 origin = self.abspath()
390 origin = self.abspath()
391 dest = self._next_class(dest).abspath()
391 dest = self._next_class(dest).abspath()
392
392
393 orig_list = origin.normcase().splitall()
393 orig_list = origin.normcase().splitall()
394 # Don't normcase dest! We want to preserve the case.
394 # Don't normcase dest! We want to preserve the case.
395 dest_list = dest.splitall()
395 dest_list = dest.splitall()
396
396
397 if orig_list[0] != self.module.normcase(dest_list[0]):
397 if orig_list[0] != self.module.normcase(dest_list[0]):
398 # Can't get here from there.
398 # Can't get here from there.
399 return dest
399 return dest
400
400
401 # Find the location where the two paths start to differ.
401 # Find the location where the two paths start to differ.
402 i = 0
402 i = 0
403 for start_seg, dest_seg in zip(orig_list, dest_list):
403 for start_seg, dest_seg in zip(orig_list, dest_list):
404 if start_seg != self.module.normcase(dest_seg):
404 if start_seg != self.module.normcase(dest_seg):
405 break
405 break
406 i += 1
406 i += 1
407
407
408 # Now i is the point where the two paths diverge.
408 # Now i is the point where the two paths diverge.
409 # Need a certain number of "os.pardir"s to work up
409 # Need a certain number of "os.pardir"s to work up
410 # from the origin to the point of divergence.
410 # from the origin to the point of divergence.
411 segments = [os.pardir] * (len(orig_list) - i)
411 segments = [os.pardir] * (len(orig_list) - i)
412 # Need to add the diverging part of dest_list.
412 # Need to add the diverging part of dest_list.
413 segments += dest_list[i:]
413 segments += dest_list[i:]
414 if len(segments) == 0:
414 if len(segments) == 0:
415 # If they happen to be identical, use os.curdir.
415 # If they happen to be identical, use os.curdir.
416 relpath = os.curdir
416 relpath = os.curdir
417 else:
417 else:
418 relpath = self.module.join(*segments)
418 relpath = self.module.join(*segments)
419 return self._next_class(relpath)
419 return self._next_class(relpath)
420
420
421 # --- Listing, searching, walking, and matching
421 # --- Listing, searching, walking, and matching
422
422
423 def listdir(self, pattern=None):
423 def listdir(self, pattern=None):
424 """ D.listdir() -> List of items in this directory.
424 """ D.listdir() -> List of items in this directory.
425
425
426 Use D.files() or D.dirs() instead if you want a listing
426 Use D.files() or D.dirs() instead if you want a listing
427 of just files or just subdirectories.
427 of just files or just subdirectories.
428
428
429 The elements of the list are path objects.
429 The elements of the list are path objects.
430
430
431 With the optional 'pattern' argument, this only lists
431 With the optional 'pattern' argument, this only lists
432 items whose names match the given pattern.
432 items whose names match the given pattern.
433 """
433 """
434 names = os.listdir(self)
434 names = os.listdir(self)
435 if pattern is not None:
435 if pattern is not None:
436 names = fnmatch.filter(names, pattern)
436 names = fnmatch.filter(names, pattern)
437 return [self / child for child in names]
437 return [self / child for child in names]
438
438
439 def dirs(self, pattern=None):
439 def dirs(self, pattern=None):
440 """ D.dirs() -> List of this directory's subdirectories.
440 """ D.dirs() -> List of this directory's subdirectories.
441
441
442 The elements of the list are path objects.
442 The elements of the list are path objects.
443 This does not walk recursively into subdirectories
443 This does not walk recursively into subdirectories
444 (but see path.walkdirs).
444 (but see path.walkdirs).
445
445
446 With the optional 'pattern' argument, this only lists
446 With the optional 'pattern' argument, this only lists
447 directories whose names match the given pattern. For
447 directories whose names match the given pattern. For
448 example, ``d.dirs('build-*')``.
448 example, ``d.dirs('build-*')``.
449 """
449 """
450 return [p for p in self.listdir(pattern) if p.isdir()]
450 return [p for p in self.listdir(pattern) if p.isdir()]
451
451
452 def files(self, pattern=None):
452 def files(self, pattern=None):
453 """ D.files() -> List of the files in this directory.
453 """ D.files() -> List of the files in this directory.
454
454
455 The elements of the list are path objects.
455 The elements of the list are path objects.
456 This does not walk into subdirectories (see path.walkfiles).
456 This does not walk into subdirectories (see path.walkfiles).
457
457
458 With the optional 'pattern' argument, this only lists files
458 With the optional 'pattern' argument, this only lists files
459 whose names match the given pattern. For example,
459 whose names match the given pattern. For example,
460 ``d.files('*.pyc')``.
460 ``d.files('*.pyc')``.
461 """
461 """
462
462
463 return [p for p in self.listdir(pattern) if p.isfile()]
463 return [p for p in self.listdir(pattern) if p.isfile()]
464
464
465 def walk(self, pattern=None, errors='strict'):
465 def walk(self, pattern=None, errors='strict'):
466 """ D.walk() -> iterator over files and subdirs, recursively.
466 """ D.walk() -> iterator over files and subdirs, recursively.
467
467
468 The iterator yields path objects naming each child item of
468 The iterator yields path objects naming each child item of
469 this directory and its descendants. This requires that
469 this directory and its descendants. This requires that
470 D.isdir().
470 D.isdir().
471
471
472 This performs a depth-first traversal of the directory tree.
472 This performs a depth-first traversal of the directory tree.
473 Each directory is returned just before all its children.
473 Each directory is returned just before all its children.
474
474
475 The errors= keyword argument controls behavior when an
475 The errors= keyword argument controls behavior when an
476 error occurs. The default is 'strict', which causes an
476 error occurs. The default is 'strict', which causes an
477 exception. The other allowed values are 'warn', which
477 exception. The other allowed values are 'warn', which
478 reports the error via warnings.warn(), and 'ignore'.
478 reports the error via warnings.warn(), and 'ignore'.
479 """
479 """
480 if errors not in ('strict', 'warn', 'ignore'):
480 if errors not in ('strict', 'warn', 'ignore'):
481 raise ValueError("invalid errors parameter")
481 raise ValueError("invalid errors parameter")
482
482
483 try:
483 try:
484 childList = self.listdir()
484 childList = self.listdir()
485 except Exception:
485 except Exception:
486 if errors == 'ignore':
486 if errors == 'ignore':
487 return
487 return
488 elif errors == 'warn':
488 elif errors == 'warn':
489 warnings.warn(
489 warnings.warn(
490 "Unable to list directory '%s': %s"
490 "Unable to list directory '%s': %s"
491 % (self, sys.exc_info()[1]),
491 % (self, sys.exc_info()[1]),
492 TreeWalkWarning)
492 TreeWalkWarning)
493 return
493 return
494 else:
494 else:
495 raise
495 raise
496
496
497 for child in childList:
497 for child in childList:
498 if pattern is None or child.fnmatch(pattern):
498 if pattern is None or child.fnmatch(pattern):
499 yield child
499 yield child
500 try:
500 try:
501 isdir = child.isdir()
501 isdir = child.isdir()
502 except Exception:
502 except Exception:
503 if errors == 'ignore':
503 if errors == 'ignore':
504 isdir = False
504 isdir = False
505 elif errors == 'warn':
505 elif errors == 'warn':
506 warnings.warn(
506 warnings.warn(
507 "Unable to access '%s': %s"
507 "Unable to access '%s': %s"
508 % (child, sys.exc_info()[1]),
508 % (child, sys.exc_info()[1]),
509 TreeWalkWarning)
509 TreeWalkWarning)
510 isdir = False
510 isdir = False
511 else:
511 else:
512 raise
512 raise
513
513
514 if isdir:
514 if isdir:
515 for item in child.walk(pattern, errors):
515 for item in child.walk(pattern, errors):
516 yield item
516 yield item
517
517
518 def walkdirs(self, pattern=None, errors='strict'):
518 def walkdirs(self, pattern=None, errors='strict'):
519 """ D.walkdirs() -> iterator over subdirs, recursively.
519 """ D.walkdirs() -> iterator over subdirs, recursively.
520
520
521 With the optional 'pattern' argument, this yields only
521 With the optional 'pattern' argument, this yields only
522 directories whose names match the given pattern. For
522 directories whose names match the given pattern. For
523 example, ``mydir.walkdirs('*test')`` yields only directories
523 example, ``mydir.walkdirs('*test')`` yields only directories
524 with names ending in 'test'.
524 with names ending in 'test'.
525
525
526 The errors= keyword argument controls behavior when an
526 The errors= keyword argument controls behavior when an
527 error occurs. The default is 'strict', which causes an
527 error occurs. The default is 'strict', which causes an
528 exception. The other allowed values are 'warn', which
528 exception. The other allowed values are 'warn', which
529 reports the error via warnings.warn(), and 'ignore'.
529 reports the error via warnings.warn(), and 'ignore'.
530 """
530 """
531 if errors not in ('strict', 'warn', 'ignore'):
531 if errors not in ('strict', 'warn', 'ignore'):
532 raise ValueError("invalid errors parameter")
532 raise ValueError("invalid errors parameter")
533
533
534 try:
534 try:
535 dirs = self.dirs()
535 dirs = self.dirs()
536 except Exception:
536 except Exception:
537 if errors == 'ignore':
537 if errors == 'ignore':
538 return
538 return
539 elif errors == 'warn':
539 elif errors == 'warn':
540 warnings.warn(
540 warnings.warn(
541 "Unable to list directory '%s': %s"
541 "Unable to list directory '%s': %s"
542 % (self, sys.exc_info()[1]),
542 % (self, sys.exc_info()[1]),
543 TreeWalkWarning)
543 TreeWalkWarning)
544 return
544 return
545 else:
545 else:
546 raise
546 raise
547
547
548 for child in dirs:
548 for child in dirs:
549 if pattern is None or child.fnmatch(pattern):
549 if pattern is None or child.fnmatch(pattern):
550 yield child
550 yield child
551 for subsubdir in child.walkdirs(pattern, errors):
551 for subsubdir in child.walkdirs(pattern, errors):
552 yield subsubdir
552 yield subsubdir
553
553
554 def walkfiles(self, pattern=None, errors='strict'):
554 def walkfiles(self, pattern=None, errors='strict'):
555 """ D.walkfiles() -> iterator over files in D, recursively.
555 """ D.walkfiles() -> iterator over files in D, recursively.
556
556
557 The optional argument, pattern, limits the results to files
557 The optional argument, pattern, limits the results to files
558 with names that match the pattern. For example,
558 with names that match the pattern. For example,
559 ``mydir.walkfiles('*.tmp')`` yields only files with the .tmp
559 ``mydir.walkfiles('*.tmp')`` yields only files with the .tmp
560 extension.
560 extension.
561 """
561 """
562 if errors not in ('strict', 'warn', 'ignore'):
562 if errors not in ('strict', 'warn', 'ignore'):
563 raise ValueError("invalid errors parameter")
563 raise ValueError("invalid errors parameter")
564
564
565 try:
565 try:
566 childList = self.listdir()
566 childList = self.listdir()
567 except Exception:
567 except Exception:
568 if errors == 'ignore':
568 if errors == 'ignore':
569 return
569 return
570 elif errors == 'warn':
570 elif errors == 'warn':
571 warnings.warn(
571 warnings.warn(
572 "Unable to list directory '%s': %s"
572 "Unable to list directory '%s': %s"
573 % (self, sys.exc_info()[1]),
573 % (self, sys.exc_info()[1]),
574 TreeWalkWarning)
574 TreeWalkWarning)
575 return
575 return
576 else:
576 else:
577 raise
577 raise
578
578
579 for child in childList:
579 for child in childList:
580 try:
580 try:
581 isfile = child.isfile()
581 isfile = child.isfile()
582 isdir = not isfile and child.isdir()
582 isdir = not isfile and child.isdir()
583 except:
583 except:
584 if errors == 'ignore':
584 if errors == 'ignore':
585 continue
585 continue
586 elif errors == 'warn':
586 elif errors == 'warn':
587 warnings.warn(
587 warnings.warn(
588 "Unable to access '%s': %s"
588 "Unable to access '%s': %s"
589 % (self, sys.exc_info()[1]),
589 % (self, sys.exc_info()[1]),
590 TreeWalkWarning)
590 TreeWalkWarning)
591 continue
591 continue
592 else:
592 else:
593 raise
593 raise
594
594
595 if isfile:
595 if isfile:
596 if pattern is None or child.fnmatch(pattern):
596 if pattern is None or child.fnmatch(pattern):
597 yield child
597 yield child
598 elif isdir:
598 elif isdir:
599 for f in child.walkfiles(pattern, errors):
599 for f in child.walkfiles(pattern, errors):
600 yield f
600 yield f
601
601
602 def fnmatch(self, pattern):
602 def fnmatch(self, pattern):
603 """ Return True if self.name matches the given pattern.
603 """ Return True if self.name matches the given pattern.
604
604
605 pattern - A filename pattern with wildcards,
605 pattern - A filename pattern with wildcards,
606 for example ``'*.py'``.
606 for example ``'*.py'``.
607 """
607 """
608 return fnmatch.fnmatch(self.name, pattern)
608 return fnmatch.fnmatch(self.name, pattern)
609
609
610 def glob(self, pattern):
610 def glob(self, pattern):
611 """ Return a list of path objects that match the pattern.
611 """ Return a list of path objects that match the pattern.
612
612
613 pattern - a path relative to this directory, with wildcards.
613 pattern - a path relative to this directory, with wildcards.
614
614
615 For example, path('/users').glob('*/bin/*') returns a list
615 For example, path('/users').glob('*/bin/*') returns a list
616 of all the files users have in their bin directories.
616 of all the files users have in their bin directories.
617 """
617 """
618 cls = self._next_class
618 cls = self._next_class
619 return [cls(s) for s in glob.glob(self / pattern)]
619 return [cls(s) for s in glob.glob(self / pattern)]
620
620
621 #
621 #
622 # --- Reading or writing an entire file at once.
622 # --- Reading or writing an entire file at once.
623
623
624 def open(self, *args, **kwargs):
624 def open(self, *args, **kwargs):
625 """ Open this file. Return a file object. """
625 """ Open this file. Return a file object. """
626 return open(self, *args, **kwargs)
626 return open(self, *args, **kwargs)
627
627
628 def bytes(self):
628 def bytes(self):
629 """ Open this file, read all bytes, return them as a string. """
629 """ Open this file, read all bytes, return them as a string. """
630 with self.open('rb') as f:
630 with self.open('rb') as f:
631 return f.read()
631 return f.read()
632
632
633 def chunks(self, size, *args, **kwargs):
633 def chunks(self, size, *args, **kwargs):
634 """ Returns a generator yielding chunks of the file, so it can
634 """ Returns a generator yielding chunks of the file, so it can
635 be read piece by piece with a simple for loop.
635 be read piece by piece with a simple for loop.
636
636
637 Any argument you pass after `size` will be passed to `open()`.
637 Any argument you pass after `size` will be passed to `open()`.
638
638
639 :example:
639 :example:
640
640
641 >>> for chunk in path("file.txt").chunk(8192):
641 >>> for chunk in path("file.txt").chunk(8192):
642 ... print(chunk)
642 ... print(chunk)
643
643
644 This will read the file by chunks of 8192 bytes.
644 This will read the file by chunks of 8192 bytes.
645 """
645 """
646 with open(self, *args, **kwargs) as f:
646 with open(self, *args, **kwargs) as f:
647 while True:
647 while True:
648 d = f.read(size)
648 d = f.read(size)
649 if not d:
649 if not d:
650 break
650 break
651 yield d
651 yield d
652
652
653 def write_bytes(self, bytes, append=False):
653 def write_bytes(self, bytes, append=False):
654 """ Open this file and write the given bytes to it.
654 """ Open this file and write the given bytes to it.
655
655
656 Default behavior is to overwrite any existing file.
656 Default behavior is to overwrite any existing file.
657 Call p.write_bytes(bytes, append=True) to append instead.
657 Call p.write_bytes(bytes, append=True) to append instead.
658 """
658 """
659 if append:
659 if append:
660 mode = 'ab'
660 mode = 'ab'
661 else:
661 else:
662 mode = 'wb'
662 mode = 'wb'
663 with self.open(mode) as f:
663 with self.open(mode) as f:
664 f.write(bytes)
664 f.write(bytes)
665
665
666 def text(self, encoding=None, errors='strict'):
666 def text(self, encoding=None, errors='strict'):
667 r""" Open this file, read it in, return the content as a string.
667 r""" Open this file, read it in, return the content as a string.
668
668
669 This method uses 'U' mode, so '\r\n' and '\r' are automatically
669 This method uses 'U' mode, so '\r\n' and '\r' are automatically
670 translated to '\n'.
670 translated to '\n'.
671
671
672 Optional arguments:
672 Optional arguments:
673
673
674 encoding - The Unicode encoding (or character set) of
674 encoding - The Unicode encoding (or character set) of
675 the file. If present, the content of the file is
675 the file. If present, the content of the file is
676 decoded and returned as a unicode object; otherwise
676 decoded and returned as a unicode object; otherwise
677 it is returned as an 8-bit str.
677 it is returned as an 8-bit str.
678 errors - How to handle Unicode errors; see help(str.decode)
678 errors - How to handle Unicode errors; see help(str.decode)
679 for the options. Default is 'strict'.
679 for the options. Default is 'strict'.
680 """
680 """
681 if encoding is None:
681 if encoding is None:
682 # 8-bit
682 # 8-bit
683 with self.open('U') as f:
683 with self.open('U') as f:
684 return f.read()
684 return f.read()
685 else:
685 else:
686 # Unicode
686 # Unicode
687 with codecs.open(self, 'r', encoding, errors) as f:
687 with codecs.open(self, 'r', encoding, errors) as f:
688 # (Note - Can't use 'U' mode here, since codecs.open
688 # (Note - Can't use 'U' mode here, since codecs.open
689 # doesn't support 'U' mode.)
689 # doesn't support 'U' mode.)
690 t = f.read()
690 t = f.read()
691 return (t.replace(u('\r\n'), u('\n'))
691 return (t.replace(u('\r\n'), u('\n'))
692 .replace(u('\r\x85'), u('\n'))
692 .replace(u('\r\x85'), u('\n'))
693 .replace(u('\r'), u('\n'))
693 .replace(u('\r'), u('\n'))
694 .replace(u('\x85'), u('\n'))
694 .replace(u('\x85'), u('\n'))
695 .replace(u('\u2028'), u('\n')))
695 .replace(u('\u2028'), u('\n')))
696
696
697 def write_text(self, text, encoding=None, errors='strict',
697 def write_text(self, text, encoding=None, errors='strict',
698 linesep=os.linesep, append=False):
698 linesep=os.linesep, append=False):
699 r""" Write the given text to this file.
699 r""" Write the given text to this file.
700
700
701 The default behavior is to overwrite any existing file;
701 The default behavior is to overwrite any existing file;
702 to append instead, use the 'append=True' keyword argument.
702 to append instead, use the 'append=True' keyword argument.
703
703
704 There are two differences between path.write_text() and
704 There are two differences between path.write_text() and
705 path.write_bytes(): newline handling and Unicode handling.
705 path.write_bytes(): newline handling and Unicode handling.
706 See below.
706 See below.
707
707
708 Parameters:
708 Parameters:
709
709
710 - text - str/unicode - The text to be written.
710 - text - str/unicode - The text to be written.
711
711
712 - encoding - str - The Unicode encoding that will be used.
712 - encoding - str - The Unicode encoding that will be used.
713 This is ignored if 'text' isn't a Unicode string.
713 This is ignored if 'text' isn't a Unicode string.
714
714
715 - errors - str - How to handle Unicode encoding errors.
715 - errors - str - How to handle Unicode encoding errors.
716 Default is 'strict'. See help(unicode.encode) for the
716 Default is 'strict'. See help(unicode.encode) for the
717 options. This is ignored if 'text' isn't a Unicode
717 options. This is ignored if 'text' isn't a Unicode
718 string.
718 string.
719
719
720 - linesep - keyword argument - str/unicode - The sequence of
720 - linesep - keyword argument - str/unicode - The sequence of
721 characters to be used to mark end-of-line. The default is
721 characters to be used to mark end-of-line. The default is
722 os.linesep. You can also specify None; this means to
722 os.linesep. You can also specify None; this means to
723 leave all newlines as they are in 'text'.
723 leave all newlines as they are in 'text'.
724
724
725 - append - keyword argument - bool - Specifies what to do if
725 - append - keyword argument - bool - Specifies what to do if
726 the file already exists (True: append to the end of it;
726 the file already exists (True: append to the end of it;
727 False: overwrite it.) The default is False.
727 False: overwrite it.) The default is False.
728
728
729
729
730 --- Newline handling.
730 --- Newline handling.
731
731
732 write_text() converts all standard end-of-line sequences
732 write_text() converts all standard end-of-line sequences
733 ('\n', '\r', and '\r\n') to your platform's default end-of-line
733 ('\n', '\r', and '\r\n') to your platform's default end-of-line
734 sequence (see os.linesep; on Windows, for example, the
734 sequence (see os.linesep; on Windows, for example, the
735 end-of-line marker is '\r\n').
735 end-of-line marker is '\r\n').
736
736
737 If you don't like your platform's default, you can override it
737 If you don't like your platform's default, you can override it
738 using the 'linesep=' keyword argument. If you specifically want
738 using the 'linesep=' keyword argument. If you specifically want
739 write_text() to preserve the newlines as-is, use 'linesep=None'.
739 write_text() to preserve the newlines as-is, use 'linesep=None'.
740
740
741 This applies to Unicode text the same as to 8-bit text, except
741 This applies to Unicode text the same as to 8-bit text, except
742 there are three additional standard Unicode end-of-line sequences:
742 there are three additional standard Unicode end-of-line sequences:
743 u'\x85', u'\r\x85', and u'\u2028'.
743 u'\x85', u'\r\x85', and u'\u2028'.
744
744
745 (This is slightly different from when you open a file for
745 (This is slightly different from when you open a file for
746 writing with fopen(filename, "w") in C or open(filename, 'w')
746 writing with fopen(filename, "w") in C or open(filename, 'w')
747 in Python.)
747 in Python.)
748
748
749
749
750 --- Unicode
750 --- Unicode
751
751
752 If 'text' isn't Unicode, then apart from newline handling, the
752 If 'text' isn't Unicode, then apart from newline handling, the
753 bytes are written verbatim to the file. The 'encoding' and
753 bytes are written verbatim to the file. The 'encoding' and
754 'errors' arguments are not used and must be omitted.
754 'errors' arguments are not used and must be omitted.
755
755
756 If 'text' is Unicode, it is first converted to bytes using the
756 If 'text' is Unicode, it is first converted to bytes using the
757 specified 'encoding' (or the default encoding if 'encoding'
757 specified 'encoding' (or the default encoding if 'encoding'
758 isn't specified). The 'errors' argument applies only to this
758 isn't specified). The 'errors' argument applies only to this
759 conversion.
759 conversion.
760
760
761 """
761 """
762 if isinstance(text, unicode):
762 if isinstance(text, unicode):
763 if linesep is not None:
763 if linesep is not None:
764 # Convert all standard end-of-line sequences to
764 # Convert all standard end-of-line sequences to
765 # ordinary newline characters.
765 # ordinary newline characters.
766 text = (text.replace(u('\r\n'), u('\n'))
766 text = (text.replace(u('\r\n'), u('\n'))
767 .replace(u('\r\x85'), u('\n'))
767 .replace(u('\r\x85'), u('\n'))
768 .replace(u('\r'), u('\n'))
768 .replace(u('\r'), u('\n'))
769 .replace(u('\x85'), u('\n'))
769 .replace(u('\x85'), u('\n'))
770 .replace(u('\u2028'), u('\n')))
770 .replace(u('\u2028'), u('\n')))
771 text = text.replace(u('\n'), linesep)
771 text = text.replace(u('\n'), linesep)
772 if encoding is None:
772 if encoding is None:
773 encoding = sys.getdefaultencoding()
773 encoding = sys.getdefaultencoding()
774 bytes = text.encode(encoding, errors)
774 bytes = text.encode(encoding, errors)
775 else:
775 else:
776 # It is an error to specify an encoding if 'text' is
776 # It is an error to specify an encoding if 'text' is
777 # an 8-bit string.
777 # an 8-bit string.
778 assert encoding is None
778 assert encoding is None
779
779
780 if linesep is not None:
780 if linesep is not None:
781 text = (text.replace('\r\n', '\n')
781 text = (text.replace('\r\n', '\n')
782 .replace('\r', '\n'))
782 .replace('\r', '\n'))
783 bytes = text.replace('\n', linesep)
783 bytes = text.replace('\n', linesep)
784
784
785 self.write_bytes(bytes, append)
785 self.write_bytes(bytes, append)
786
786
787 def lines(self, encoding=None, errors='strict', retain=True):
787 def lines(self, encoding=None, errors='strict', retain=True):
788 r""" Open this file, read all lines, return them in a list.
788 r""" Open this file, read all lines, return them in a list.
789
789
790 Optional arguments:
790 Optional arguments:
791 encoding - The Unicode encoding (or character set) of
791 encoding - The Unicode encoding (or character set) of
792 the file. The default is None, meaning the content
792 the file. The default is None, meaning the content
793 of the file is read as 8-bit characters and returned
793 of the file is read as 8-bit characters and returned
794 as a list of (non-Unicode) str objects.
794 as a list of (non-Unicode) str objects.
795 errors - How to handle Unicode errors; see help(str.decode)
795 errors - How to handle Unicode errors; see help(str.decode)
796 for the options. Default is 'strict'
796 for the options. Default is 'strict'
797 retain - If true, retain newline characters; but all newline
797 retain - If true, retain newline characters; but all newline
798 character combinations ('\r', '\n', '\r\n') are
798 character combinations ('\r', '\n', '\r\n') are
799 translated to '\n'. If false, newline characters are
799 translated to '\n'. If false, newline characters are
800 stripped off. Default is True.
800 stripped off. Default is True.
801
801
802 This uses 'U' mode.
802 This uses 'U' mode.
803 """
803 """
804 if encoding is None and retain:
804 if encoding is None and retain:
805 with self.open('U') as f:
805 with self.open('U') as f:
806 return f.readlines()
806 return f.readlines()
807 else:
807 else:
808 return self.text(encoding, errors).splitlines(retain)
808 return self.text(encoding, errors).splitlines(retain)
809
809
810 def write_lines(self, lines, encoding=None, errors='strict',
810 def write_lines(self, lines, encoding=None, errors='strict',
811 linesep=os.linesep, append=False):
811 linesep=os.linesep, append=False):
812 r""" Write the given lines of text to this file.
812 r""" Write the given lines of text to this file.
813
813
814 By default this overwrites any existing file at this path.
814 By default this overwrites any existing file at this path.
815
815
816 This puts a platform-specific newline sequence on every line.
816 This puts a platform-specific newline sequence on every line.
817 See 'linesep' below.
817 See 'linesep' below.
818
818
819 lines - A list of strings.
819 lines - A list of strings.
820
820
821 encoding - A Unicode encoding to use. This applies only if
821 encoding - A Unicode encoding to use. This applies only if
822 'lines' contains any Unicode strings.
822 'lines' contains any Unicode strings.
823
823
824 errors - How to handle errors in Unicode encoding. This
824 errors - How to handle errors in Unicode encoding. This
825 also applies only to Unicode strings.
825 also applies only to Unicode strings.
826
826
827 linesep - The desired line-ending. This line-ending is
827 linesep - The desired line-ending. This line-ending is
828 applied to every line. If a line already has any
828 applied to every line. If a line already has any
829 standard line ending ('\r', '\n', '\r\n', u'\x85',
829 standard line ending ('\r', '\n', '\r\n', u'\x85',
830 u'\r\x85', u'\u2028'), that will be stripped off and
830 u'\r\x85', u'\u2028'), that will be stripped off and
831 this will be used instead. The default is os.linesep,
831 this will be used instead. The default is os.linesep,
832 which is platform-dependent ('\r\n' on Windows, '\n' on
832 which is platform-dependent ('\r\n' on Windows, '\n' on
833 Unix, etc.) Specify None to write the lines as-is,
833 Unix, etc.) Specify None to write the lines as-is,
834 like file.writelines().
834 like file.writelines().
835
835
836 Use the keyword argument append=True to append lines to the
836 Use the keyword argument append=True to append lines to the
837 file. The default is to overwrite the file. Warning:
837 file. The default is to overwrite the file. Warning:
838 When you use this with Unicode data, if the encoding of the
838 When you use this with Unicode data, if the encoding of the
839 existing data in the file is different from the encoding
839 existing data in the file is different from the encoding
840 you specify with the encoding= parameter, the result is
840 you specify with the encoding= parameter, the result is
841 mixed-encoding data, which can really confuse someone trying
841 mixed-encoding data, which can really confuse someone trying
842 to read the file later.
842 to read the file later.
843 """
843 """
844 if append:
844 if append:
845 mode = 'ab'
845 mode = 'ab'
846 else:
846 else:
847 mode = 'wb'
847 mode = 'wb'
848 with self.open(mode) as f:
848 with self.open(mode) as f:
849 for line in lines:
849 for line in lines:
850 isUnicode = isinstance(line, unicode)
850 isUnicode = isinstance(line, unicode)
851 if linesep is not None:
851 if linesep is not None:
852 # Strip off any existing line-end and add the
852 # Strip off any existing line-end and add the
853 # specified linesep string.
853 # specified linesep string.
854 if isUnicode:
854 if isUnicode:
855 if line[-2:] in (u('\r\n'), u('\x0d\x85')):
855 if line[-2:] in (u('\r\n'), u('\x0d\x85')):
856 line = line[:-2]
856 line = line[:-2]
857 elif line[-1:] in (u('\r'), u('\n'),
857 elif line[-1:] in (u('\r'), u('\n'),
858 u('\x85'), u('\u2028')):
858 u('\x85'), u('\u2028')):
859 line = line[:-1]
859 line = line[:-1]
860 else:
860 else:
861 if line[-2:] == '\r\n':
861 if line[-2:] == '\r\n':
862 line = line[:-2]
862 line = line[:-2]
863 elif line[-1:] in ('\r', '\n'):
863 elif line[-1:] in ('\r', '\n'):
864 line = line[:-1]
864 line = line[:-1]
865 line += linesep
865 line += linesep
866 if isUnicode:
866 if isUnicode:
867 if encoding is None:
867 if encoding is None:
868 encoding = sys.getdefaultencoding()
868 encoding = sys.getdefaultencoding()
869 line = line.encode(encoding, errors)
869 line = line.encode(encoding, errors)
870 f.write(line)
870 f.write(line)
871
871
872 def read_md5(self):
872 def read_md5(self):
873 """ Calculate the md5 hash for this file.
873 """ Calculate the md5 hash for this file.
874
874
875 This reads through the entire file.
875 This reads through the entire file.
876 """
876 """
877 return self.read_hash('md5')
877 return self.read_hash('md5')
878
878
879 def _hash(self, hash_name):
879 def _hash(self, hash_name):
880 """ Returns a hash object for the file at the current path.
880 """ Returns a hash object for the file at the current path.
881
881
882 `hash_name` should be a hash algo name such as 'md5' or 'sha1'
882 `hash_name` should be a hash algo name such as 'md5' or 'sha1'
883 that's available in the `hashlib` module.
883 that's available in the `hashlib` module.
884 """
884 """
885 m = hashlib.new(hash_name)
885 m = hashlib.new(hash_name)
886 for chunk in self.chunks(8192):
886 for chunk in self.chunks(8192):
887 m.update(chunk)
887 m.update(chunk)
888 return m
888 return m
889
889
890 def read_hash(self, hash_name):
890 def read_hash(self, hash_name):
891 """ Calculate given hash for this file.
891 """ Calculate given hash for this file.
892
892
893 List of supported hashes can be obtained from hashlib package. This
893 List of supported hashes can be obtained from hashlib package. This
894 reads the entire file.
894 reads the entire file.
895 """
895 """
896 return self._hash(hash_name).digest()
896 return self._hash(hash_name).digest()
897
897
898 def read_hexhash(self, hash_name):
898 def read_hexhash(self, hash_name):
899 """ Calculate given hash for this file, returning hexdigest.
899 """ Calculate given hash for this file, returning hexdigest.
900
900
901 List of supported hashes can be obtained from hashlib package. This
901 List of supported hashes can be obtained from hashlib package. This
902 reads the entire file.
902 reads the entire file.
903 """
903 """
904 return self._hash(hash_name).hexdigest()
904 return self._hash(hash_name).hexdigest()
905
905
906 # --- Methods for querying the filesystem.
906 # --- Methods for querying the filesystem.
907 # N.B. On some platforms, the os.path functions may be implemented in C
907 # N.B. On some platforms, the os.path functions may be implemented in C
908 # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get
908 # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get
909 # bound. Playing it safe and wrapping them all in method calls.
909 # bound. Playing it safe and wrapping them all in method calls.
910
910
911 def isabs(self):
911 def isabs(self):
912 return self.module.isabs(self)
912 return self.module.isabs(self)
913
913
914 def exists(self):
914 def exists(self):
915 return self.module.exists(self)
915 return self.module.exists(self)
916
916
917 def isdir(self):
917 def isdir(self):
918 return self.module.isdir(self)
918 return self.module.isdir(self)
919
919
920 def isfile(self):
920 def isfile(self):
921 return self.module.isfile(self)
921 return self.module.isfile(self)
922
922
923 def islink(self):
923 def islink(self):
924 return self.module.islink(self)
924 return self.module.islink(self)
925
925
926 def ismount(self):
926 def ismount(self):
927 return self.module.ismount(self)
927 return self.module.ismount(self)
928
928
929 def samefile(self, other):
929 def samefile(self, other):
930 return self.module.samefile(self, other)
930 return self.module.samefile(self, other)
931
931
932 def getatime(self):
932 def getatime(self):
933 return self.module.getatime(self)
933 return self.module.getatime(self)
934
934
935 atime = property(
935 atime = property(
936 getatime, None, None,
936 getatime, None, None,
937 """ Last access time of the file. """)
937 """ Last access time of the file. """)
938
938
939 def getmtime(self):
939 def getmtime(self):
940 return self.module.getmtime(self)
940 return self.module.getmtime(self)
941
941
942 mtime = property(
942 mtime = property(
943 getmtime, None, None,
943 getmtime, None, None,
944 """ Last-modified time of the file. """)
944 """ Last-modified time of the file. """)
945
945
946 def getctime(self):
946 def getctime(self):
947 return self.module.getctime(self)
947 return self.module.getctime(self)
948
948
949 ctime = property(
949 ctime = property(
950 getctime, None, None,
950 getctime, None, None,
951 """ Creation time of the file. """)
951 """ Creation time of the file. """)
952
952
953 def getsize(self):
953 def getsize(self):
954 return self.module.getsize(self)
954 return self.module.getsize(self)
955
955
956 size = property(
956 size = property(
957 getsize, None, None,
957 getsize, None, None,
958 """ Size of the file, in bytes. """)
958 """ Size of the file, in bytes. """)
959
959
960 if hasattr(os, 'access'):
960 if hasattr(os, 'access'):
961 def access(self, mode):
961 def access(self, mode):
962 """ Return true if current user has access to this path.
962 """ Return true if current user has access to this path.
963
963
964 mode - One of the constants os.F_OK, os.R_OK, os.W_OK, os.X_OK
964 mode - One of the constants os.F_OK, os.R_OK, os.W_OK, os.X_OK
965 """
965 """
966 return os.access(self, mode)
966 return os.access(self, mode)
967
967
968 def stat(self):
968 def stat(self):
969 """ Perform a stat() system call on this path. """
969 """ Perform a stat() system call on this path. """
970 return os.stat(self)
970 return os.stat(self)
971
971
972 def lstat(self):
972 def lstat(self):
973 """ Like path.stat(), but do not follow symbolic links. """
973 """ Like path.stat(), but do not follow symbolic links. """
974 return os.lstat(self)
974 return os.lstat(self)
975
975
976 def __get_owner_windows(self):
976 def __get_owner_windows(self):
977 r"""
977 r"""
978 Return the name of the owner of this file or directory. Follow
978 Return the name of the owner of this file or directory. Follow
979 symbolic links.
979 symbolic links.
980
980
981 Return a name of the form ur'DOMAIN\User Name'; may be a group.
981 Return a name of the form ur'DOMAIN\User Name'; may be a group.
982 """
982 """
983 desc = win32security.GetFileSecurity(
983 desc = win32security.GetFileSecurity(
984 self, win32security.OWNER_SECURITY_INFORMATION)
984 self, win32security.OWNER_SECURITY_INFORMATION)
985 sid = desc.GetSecurityDescriptorOwner()
985 sid = desc.GetSecurityDescriptorOwner()
986 account, domain, typecode = win32security.LookupAccountSid(None, sid)
986 account, domain, typecode = win32security.LookupAccountSid(None, sid)
987 return domain + u('\\') + account
987 return domain + u('\\') + account
988
988
989 def __get_owner_unix(self):
989 def __get_owner_unix(self):
990 """
990 """
991 Return the name of the owner of this file or directory. Follow
991 Return the name of the owner of this file or directory. Follow
992 symbolic links.
992 symbolic links.
993 """
993 """
994 st = self.stat()
994 st = self.stat()
995 return pwd.getpwuid(st.st_uid).pw_name
995 return pwd.getpwuid(st.st_uid).pw_name
996
996
997 def __get_owner_not_implemented(self):
997 def __get_owner_not_implemented(self):
998 raise NotImplementedError("Ownership not available on this platform.")
998 raise NotImplementedError("Ownership not available on this platform.")
999
999
1000 if 'win32security' in globals():
1000 if 'win32security' in globals():
1001 get_owner = __get_owner_windows
1001 get_owner = __get_owner_windows
1002 elif 'pwd' in globals():
1002 elif 'pwd' in globals():
1003 get_owner = __get_owner_unix
1003 get_owner = __get_owner_unix
1004 else:
1004 else:
1005 get_owner = __get_owner_not_implemented
1005 get_owner = __get_owner_not_implemented
1006
1006
1007 owner = property(
1007 owner = property(
1008 get_owner, None, None,
1008 get_owner, None, None,
1009 """ Name of the owner of this file or directory. """)
1009 """ Name of the owner of this file or directory. """)
1010
1010
1011 if hasattr(os, 'statvfs'):
1011 if hasattr(os, 'statvfs'):
1012 def statvfs(self):
1012 def statvfs(self):
1013 """ Perform a statvfs() system call on this path. """
1013 """ Perform a statvfs() system call on this path. """
1014 return os.statvfs(self)
1014 return os.statvfs(self)
1015
1015
1016 if hasattr(os, 'pathconf'):
1016 if hasattr(os, 'pathconf'):
1017 def pathconf(self, name):
1017 def pathconf(self, name):
1018 return os.pathconf(self, name)
1018 return os.pathconf(self, name)
1019
1019
1020 #
1020 #
1021 # --- Modifying operations on files and directories
1021 # --- Modifying operations on files and directories
1022
1022
1023 def utime(self, times):
1023 def utime(self, times):
1024 """ Set the access and modified times of this file. """
1024 """ Set the access and modified times of this file. """
1025 os.utime(self, times)
1025 os.utime(self, times)
1026 return self
1026 return self
1027
1027
1028 def chmod(self, mode):
1028 def chmod(self, mode):
1029 os.chmod(self, mode)
1029 os.chmod(self, mode)
1030 return self
1030 return self
1031
1031
1032 if hasattr(os, 'chown'):
1032 if hasattr(os, 'chown'):
1033 def chown(self, uid=-1, gid=-1):
1033 def chown(self, uid=-1, gid=-1):
1034 os.chown(self, uid, gid)
1034 os.chown(self, uid, gid)
1035 return self
1035 return self
1036
1036
1037 def rename(self, new):
1037 def rename(self, new):
1038 os.rename(self, new)
1038 os.rename(self, new)
1039 return self._next_class(new)
1039 return self._next_class(new)
1040
1040
1041 def renames(self, new):
1041 def renames(self, new):
1042 os.renames(self, new)
1042 os.renames(self, new)
1043 return self._next_class(new)
1043 return self._next_class(new)
1044
1044
1045 #
1045 #
1046 # --- Create/delete operations on directories
1046 # --- Create/delete operations on directories
1047
1047
1048 def mkdir(self, mode=o777):
1048 def mkdir(self, mode=o777):
1049 os.mkdir(self, mode)
1049 os.mkdir(self, mode)
1050 return self
1050 return self
1051
1051
1052 def mkdir_p(self, mode=o777):
1052 def mkdir_p(self, mode=o777):
1053 try:
1053 try:
1054 self.mkdir(mode)
1054 self.mkdir(mode)
1055 except OSError:
1055 except OSError:
1056 _, e, _ = sys.exc_info()
1056 _, e, _ = sys.exc_info()
1057 if e.errno != errno.EEXIST:
1057 if e.errno != errno.EEXIST:
1058 raise
1058 raise
1059 return self
1059 return self
1060
1060
1061 def makedirs(self, mode=o777):
1061 def makedirs(self, mode=o777):
1062 os.makedirs(self, mode)
1062 os.makedirs(self, mode)
1063 return self
1063 return self
1064
1064
1065 def makedirs_p(self, mode=o777):
1065 def makedirs_p(self, mode=o777):
1066 try:
1066 try:
1067 self.makedirs(mode)
1067 self.makedirs(mode)
1068 except OSError:
1068 except OSError:
1069 _, e, _ = sys.exc_info()
1069 _, e, _ = sys.exc_info()
1070 if e.errno != errno.EEXIST:
1070 if e.errno != errno.EEXIST:
1071 raise
1071 raise
1072 return self
1072 return self
1073
1073
1074 def rmdir(self):
1074 def rmdir(self):
1075 os.rmdir(self)
1075 os.rmdir(self)
1076 return self
1076 return self
1077
1077
1078 def rmdir_p(self):
1078 def rmdir_p(self):
1079 try:
1079 try:
1080 self.rmdir()
1080 self.rmdir()
1081 except OSError:
1081 except OSError:
1082 _, e, _ = sys.exc_info()
1082 _, e, _ = sys.exc_info()
1083 if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
1083 if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
1084 raise
1084 raise
1085 return self
1085 return self
1086
1086
1087 def removedirs(self):
1087 def removedirs(self):
1088 os.removedirs(self)
1088 os.removedirs(self)
1089 return self
1089 return self
1090
1090
1091 def removedirs_p(self):
1091 def removedirs_p(self):
1092 try:
1092 try:
1093 self.removedirs()
1093 self.removedirs()
1094 except OSError:
1094 except OSError:
1095 _, e, _ = sys.exc_info()
1095 _, e, _ = sys.exc_info()
1096 if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
1096 if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
1097 raise
1097 raise
1098 return self
1098 return self
1099
1099
1100 # --- Modifying operations on files
1100 # --- Modifying operations on files
1101
1101
1102 def touch(self):
1102 def touch(self):
1103 """ Set the access/modified times of this file to the current time.
1103 """ Set the access/modified times of this file to the current time.
1104 Create the file if it does not exist.
1104 Create the file if it does not exist.
1105 """
1105 """
1106 fd = os.open(self, os.O_WRONLY | os.O_CREAT, o666)
1106 fd = os.open(self, os.O_WRONLY | os.O_CREAT, o666)
1107 os.close(fd)
1107 os.close(fd)
1108 os.utime(self, None)
1108 os.utime(self, None)
1109 return self
1109 return self
1110
1110
1111 def remove(self):
1111 def remove(self):
1112 os.remove(self)
1112 os.remove(self)
1113 return self
1113 return self
1114
1114
1115 def remove_p(self):
1115 def remove_p(self):
1116 try:
1116 try:
1117 self.unlink()
1117 self.unlink()
1118 except OSError:
1118 except OSError:
1119 _, e, _ = sys.exc_info()
1119 _, e, _ = sys.exc_info()
1120 if e.errno != errno.ENOENT:
1120 if e.errno != errno.ENOENT:
1121 raise
1121 raise
1122 return self
1122 return self
1123
1123
1124 def unlink(self):
1124 def unlink(self):
1125 os.unlink(self)
1125 os.unlink(self)
1126 return self
1126 return self
1127
1127
1128 def unlink_p(self):
1128 def unlink_p(self):
1129 self.remove_p()
1129 self.remove_p()
1130 return self
1130 return self
1131
1131
1132 # --- Links
1132 # --- Links
1133
1133
1134 if hasattr(os, 'link'):
1134 if hasattr(os, 'link'):
1135 def link(self, newpath):
1135 def link(self, newpath):
1136 """ Create a hard link at 'newpath', pointing to this file. """
1136 """ Create a hard link at 'newpath', pointing to this file. """
1137 os.link(self, newpath)
1137 os.link(self, newpath)
1138 return self._next_class(newpath)
1138 return self._next_class(newpath)
1139
1139
1140 if hasattr(os, 'symlink'):
1140 if hasattr(os, 'symlink'):
1141 def symlink(self, newlink):
1141 def symlink(self, newlink):
1142 """ Create a symbolic link at 'newlink', pointing here. """
1142 """ Create a symbolic link at 'newlink', pointing here. """
1143 os.symlink(self, newlink)
1143 os.symlink(self, newlink)
1144 return self._next_class(newlink)
1144 return self._next_class(newlink)
1145
1145
1146 if hasattr(os, 'readlink'):
1146 if hasattr(os, 'readlink'):
1147 def readlink(self):
1147 def readlink(self):
1148 """ Return the path to which this symbolic link points.
1148 """ Return the path to which this symbolic link points.
1149
1149
1150 The result may be an absolute or a relative path.
1150 The result may be an absolute or a relative path.
1151 """
1151 """
1152 return self._next_class(os.readlink(self))
1152 return self._next_class(os.readlink(self))
1153
1153
1154 def readlinkabs(self):
1154 def readlinkabs(self):
1155 """ Return the path to which this symbolic link points.
1155 """ Return the path to which this symbolic link points.
1156
1156
1157 The result is always an absolute path.
1157 The result is always an absolute path.
1158 """
1158 """
1159 p = self.readlink()
1159 p = self.readlink()
1160 if p.isabs():
1160 if p.isabs():
1161 return p
1161 return p
1162 else:
1162 else:
1163 return (self.parent / p).abspath()
1163 return (self.parent / p).abspath()
1164
1164
1165 #
1165 #
1166 # --- High-level functions from shutil
1166 # --- High-level functions from shutil
1167
1167
1168 copyfile = shutil.copyfile
1168 copyfile = shutil.copyfile
1169 copymode = shutil.copymode
1169 copymode = shutil.copymode
1170 copystat = shutil.copystat
1170 copystat = shutil.copystat
1171 copy = shutil.copy
1171 copy = shutil.copy
1172 copy2 = shutil.copy2
1172 copy2 = shutil.copy2
1173 copytree = shutil.copytree
1173 copytree = shutil.copytree
1174 if hasattr(shutil, 'move'):
1174 if hasattr(shutil, 'move'):
1175 move = shutil.move
1175 move = shutil.move
1176 rmtree = shutil.rmtree
1176 rmtree = shutil.rmtree
1177
1177
1178 def rmtree_p(self):
1178 def rmtree_p(self):
1179 try:
1179 try:
1180 self.rmtree()
1180 self.rmtree()
1181 except OSError:
1181 except OSError:
1182 _, e, _ = sys.exc_info()
1182 _, e, _ = sys.exc_info()
1183 if e.errno != errno.ENOENT:
1183 if e.errno != errno.ENOENT:
1184 raise
1184 raise
1185 return self
1185 return self
1186
1186
1187 def chdir(self):
1187 def chdir(self):
1188 os.chdir(self)
1188 os.chdir(self)
1189
1189
1190 cd = chdir
1190 cd = chdir
1191
1191
1192 #
1192 #
1193 # --- Special stuff from os
1193 # --- Special stuff from os
1194
1194
1195 if hasattr(os, 'chroot'):
1195 if hasattr(os, 'chroot'):
1196 def chroot(self):
1196 def chroot(self):
1197 os.chroot(self)
1197 os.chroot(self)
1198
1198
1199 if hasattr(os, 'startfile'):
1199 if hasattr(os, 'startfile'):
1200 def startfile(self):
1200 def startfile(self):
1201 os.startfile(self)
1201 os.startfile(self)
1202 return self
1202 return self
1203
1203
1204
1204
1205 class tempdir(path):
1205 class tempdir(path):
1206 """
1206 """
1207 A temporary directory via tempfile.mkdtemp, and constructed with the
1207 A temporary directory via tempfile.mkdtemp, and constructed with the
1208 same parameters that you can use as a context manager.
1208 same parameters that you can use as a context manager.
1209
1209
1210 Example:
1210 Example:
1211
1211
1212 with tempdir() as d:
1212 with tempdir() as d:
1213 # do stuff with the path object "d"
1213 # do stuff with the path object "d"
1214
1214
1215 # here the directory is deleted automatically
1215 # here the directory is deleted automatically
1216 """
1216 """
1217
1217
1218 @ClassProperty
1218 @ClassProperty
1219 @classmethod
1219 @classmethod
1220 def _next_class(cls):
1220 def _next_class(cls):
1221 return path
1221 return path
1222
1222
1223 def __new__(cls, *args, **kwargs):
1223 def __new__(cls, *args, **kwargs):
1224 dirname = tempfile.mkdtemp(*args, **kwargs)
1224 dirname = tempfile.mkdtemp(*args, **kwargs)
1225 return super(tempdir, cls).__new__(cls, dirname)
1225 return super(tempdir, cls).__new__(cls, dirname)
1226
1226
1227 def __init__(self, *args, **kwargs):
1227 def __init__(self, *args, **kwargs):
1228 pass
1228 pass
1229
1229
1230 def __enter__(self):
1230 def __enter__(self):
1231 return self
1231 return self
1232
1232
1233 def __exit__(self, exc_type, exc_value, traceback):
1233 def __exit__(self, exc_type, exc_value, traceback):
1234 if not exc_value:
1234 if not exc_value:
1235 self.rmtree()
1235 self.rmtree()
1236
1236
1237
1237
1238 def _permission_mask(mode):
1238 def _permission_mask(mode):
1239 """
1239 """
1240 Convert a Unix chmod symbolic mode like 'ugo+rwx' to a function
1240 Convert a Unix chmod symbolic mode like 'ugo+rwx' to a function
1241 suitable for applying to a mask to affect that change.
1241 suitable for applying to a mask to affect that change.
1242
1242
1243 >>> mask = _permission_mask('ugo+rwx')
1243 >>> mask = _permission_mask('ugo+rwx')
1244 >>> oct(mask(o554))
1244 >>> oct(mask(o554))
1245 'o777'
1245 'o777'
1246
1246
1247 >>> oct(_permission_mask('gw-x')(o777))
1247 >>> oct(_permission_mask('gw-x')(o777))
1248 'o766'
1248 'o766'
1249 """
1249 """
1250 parsed = re.match('(?P<who>[ugo]+)(?P<op>[-+])(?P<what>[rwx]+)$', mode)
1250 parsed = re.match('(?P<who>[ugo]+)(?P<op>[-+])(?P<what>[rwx]+)$', mode)
1251 if not parsed:
1251 if not parsed:
1252 raise ValueError("Unrecognized symbolic mode", mode)
1252 raise ValueError("Unrecognized symbolic mode", mode)
1253 spec_map = dict(r=4, w=2, x=1)
1253 spec_map = dict(r=4, w=2, x=1)
1254 spec = reduce(operator.or_, [spec_map[perm]
1254 spec = reduce(operator.or_, [spec_map[perm]
1255 for perm in parsed.group('what')])
1255 for perm in parsed.group('what')])
1256 # now apply spec to each in who
1256 # now apply spec to each in who
1257 shift_map = dict(u=6, g=3, o=0)
1257 shift_map = dict(u=6, g=3, o=0)
1258 mask = reduce(operator.or_, [spec << shift_map[subj]
1258 mask = reduce(operator.or_, [spec << shift_map[subj]
1259 for subj in parsed.group('who')])
1259 for subj in parsed.group('who')])
1260
1260
1261 op = parsed.group('op')
1261 op = parsed.group('op')
1262 # if op is -, invert the mask
1262 # if op is -, invert the mask
1263 if op == '-':
1263 if op == '-':
1264 mask ^= o777
1264 mask ^= o777
1265
1265
1266 op_map = {'+': operator.or_, '-': operator.and_}
1266 op_map = {'+': operator.or_, '-': operator.and_}
1267 return functools.partial(op_map[op], mask)
1267 return functools.partial(op_map[op], mask)
General Comments 0
You need to be logged in to leave comments. Login now