Completed
Push — master ( b3ec73...797e4a )
by Bjorn
26s
created

Path.__new__()   A

Complexity

Conditions 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
c 0
b 0
f 0
dl 0
loc 5
rs 9.4285
1
# -*- coding: utf-8 -*-
2
"""Poor man's pathlib.
3
4
   (Path instances are subclasses of str, so interoperability with existing
5
   os.path code is greater than with Python 3's pathlib.)
6
"""
7
# pylint:disable=C0111,R0904
8
# R0904: too many public methods in Path
9
from __future__ import print_function
10
import os
11
import re
12
from contextlib import contextmanager
13
import shutil
14
15
16
def doc(srcfn):
17
    def decorator(fn):
18
        if srcfn.__doc__ is not None:
19
            fn.__doc__ = srcfn.__doc__.replace(srcfn.__name__, fn.__name__)
20
        return fn
21
    return decorator
22
23
24
class Path(str):
25
    """Poor man's pathlib.
26
    """
27
28
    def __new__(cls, *args, **kw):
29
        if isinstance(args[0], Path):
30
            return str.__new__(cls, str(args[0]), **kw)
31
        else:
32
            return str.__new__(cls, os.path.normcase(args[0]), **kw)
33
34
    def __div__(self, other):
35
        return Path(
36
            os.path.normcase(
37
                os.path.normpath(
38
                    os.path.join(self, other)
39
                )
40
            )
41
        )
42
    __truediv__ = __div__
43
44
    @doc(os.unlink)
45
    def unlink(self):
46
        os.unlink(self)
47
48
    def open(self, mode='r'):
49
        return open(self, mode)
50
51
    def read(self, mode='r'):
52
        with self.open(mode) as fp:
53
            return fp.read()
54
55
    def write(self, txt, mode='w'):
56
        with self.open(mode) as fp:
57
            fp.write(txt)
58
59
    def append(self, txt, mode='a'):
60
        with self.open(mode) as fp:
61
            fp.write(txt)
62
63
    def __iter__(self):
64
        for root, dirs, files in os.walk(self):
65
            dotdirs = [d for d in dirs if d.startswith('.')]
66
            for d in dotdirs:
67
                dirs.remove(d)
68
            dotfiles = [d for d in files if d.startswith('.')]
69
            for d in dotfiles:
70
                files.remove(d)
71
            for fname in files:
72
                yield Path(os.path.join(root, fname))
73
74
    def __contains__(self, item):
75
        if self.isdir():
76
            return item in self.listdir()
77
        super(Path, self).__contains__(item)
78
79
    @doc(shutil.rmtree)
80
    def rmtree(self, subdir=None):
81
        if subdir is not None:
82
            shutil.rmtree(self / subdir, ignore_errors=True)
83
        else:
84
            shutil.rmtree(self, ignore_errors=True)
85
86
    def contents(self):
87
        res = [d.relpath(self) for d in self.glob('**/*')]
88
        res.sort()
89
        return res
90
91
    @classmethod
92
    def curdir(cls):
93
        """Initialize a Path object on the current directory.
94
        """
95
        return cls(os.getcwd())
96
97
    def touch(self, mode=0o666, exist_ok=True):
98
        """Create this file with the given access mode, if it doesn't exist.
99
           Based on:
100
            
101
              https://github.com/python/cpython/blob/master/Lib/pathlib.py)
102
              
103
        """
104
        if exist_ok:
105
            # First try to bump modification time
106
            # Implementation note: GNU touch uses the UTIME_NOW option of
107
            # the utimensat() / futimens() functions.
108
            try:
109
                os.utime(self, None)
110
            except OSError:
111
                # Avoid exception chaining
112
                pass
113
            else:
114
                return
115
        flags = os.O_CREAT | os.O_WRONLY
116
        if not exist_ok:
117
            flags |= os.O_EXCL
118
        fd = os.open(self, flags, mode)
119
        os.close(fd)
120
121
    def glob(self, pat):
122
        """`pat` can be an extended glob pattern, e.g. `'**/*.less'`
123
           This code handles negations similarly to node.js' minimatch, i.e.
124
           a leading `!` will negate the entire pattern.
125
        """
126
        r = ""
127
        negate = int(pat.startswith('!'))
128
        i = negate
129
130
        while i < len(pat):
131
            if pat[i:i + 3] == '**/':
132
                r += "(?:.*/)?"
133
                i += 3
134
            elif pat[i] == "*":
135
                r += "[^/]*"
136
                i += 1
137
            elif pat[i] == ".":
138
                r += "[.]"
139
                i += 1
140
            elif pat[i] == "?":
141
                r += "."
142
                i += 1
143
            else:
144
                r += pat[i]
145
                i += 1
146
        r += r'\Z(?ms)'
147
        # print '\n\npat', pat
148
        # print 'regex:', r
149
        # print [s.relpath(self).replace('\\', '/') for s in self]
150
        rx = re.compile(r)
151
152
        def match(d):
153
            m = rx.match(d)
154
            return not m if negate else m
155
156
        return [s for s in self if match(s.relpath(self).replace('\\', '/'))]
157
158
    @doc(os.path.abspath)
159
    def abspath(self):
160
        return Path(os.path.abspath(self))
161
    absolute = abspath  # pathlib
162
163
    def drive(self):
164
        """Return the drive of `self`.
165
        """
166
        return self.splitdrive()[0]
167
168
    def drivepath(self):
169
        """The path local to this drive (i.e. remove drive letter).
170
        """
171
        return self.splitdrive()[1]
172
173
    @doc(os.path.basename)
174
    def basename(self):
175
        return Path(os.path.basename(self))
176
177
    @doc(os.path.commonprefix)
178
    def commonprefix(self, *args):
179
        return os.path.commonprefix([str(self)] + [str(a) for a in args])
180
181
    @doc(os.path.dirname)
182
    def dirname(self):
183
        return Path(os.path.dirname(self))
184
185
    @doc(os.path.exists)
186
    def exists(self):
187
        return os.path.exists(self)
188
189
    @doc(os.path.expanduser)
190
    def expanduser(self):
191
        return Path(os.path.expanduser(self))
192
193
    @doc(os.path.expandvars)
194
    def expandvars(self):
195
        return Path(os.path.expandvars(self))
196
197
    @doc(os.path.getatime)
198
    def getatime(self):
199
        return os.path.getatime(self)
200
201
    @doc(os.path.getctime)
202
    def getctime(self):
203
        return os.path.getctime(self)
204
205
    @doc(os.path.getmtime)
206
    def getmtime(self):
207
        return os.path.getmtime(self)
208
209
    @doc(os.path.getsize)
210
    def getsize(self):
211
        return os.path.getsize(self)
212
213
    @doc(os.path.isabs)
214
    def isabs(self):
215
        return os.path.isabs(self)
216
217
    @doc(os.path.isdir)
218
    def isdir(self, *args, **kw):
219
        return os.path.isdir(self, *args, **kw)
220
221
    @doc(os.path.isfile)
222
    def isfile(self):
223
        return os.path.isfile(self)
224
225
    @doc(os.path.islink)
226
    def islink(self):
227
        return os.path.islink(self)
228
229
    @doc(os.path.ismount)
230
    def ismount(self):
231
        return os.path.ismount(self)
232
233
    @doc(os.path.join)
234
    def join(self, *args):
235
        return Path(os.path.join(self, *args))
236
237
    @doc(os.path.lexists)
238
    def lexists(self):
239
        return os.path.lexists(self)
240
241
    @doc(os.path.normcase)
242
    def normcase(self):
243
        return Path(os.path.normcase(self))
244
245
    @doc(os.path.normpath)
246
    def normpath(self):
247
        return Path(os.path.normpath(str(self)))
248
249
    @doc(os.path.realpath)
250
    def realpath(self):
251
        return Path(os.path.realpath(self))
252
253
    @doc(os.path.relpath)
254
    def relpath(self, other=""):
255
        return Path(os.path.relpath(str(self), str(other)))
256
257
    @doc(os.path.split)
258
    def split(self, sep=None, maxsplit=-1):
259
        # some heuristics to determine if this is a str.split call or
260
        # a os.split call...
261
        sval = str(self)
262
        if sep is not None or ' ' in sval:
263
            return sval.split(sep or ' ', maxsplit)
264
        return os.path.split(self)
265
266
    def parts(self):
267
        res = re.split(r"[\\/]", self)
268
        if res and os.path.splitdrive(res[0]) == (res[0], ''):
269
            res[0] += os.path.sep
270
        return res
271
272
    def parent_iter(self):
273
        parts = self.abspath().normpath().normcase().parts()
274
        for i in range(1, len(parts)):
275
            yield Path(os.path.join(*parts[:-i]))
276
277
    @property
278
    def parents(self):
279
        return list(self.parent_iter())
280
281
    @property
282
    def parent(self):
283
        return self.parents[0]
284
285
    @doc(os.path.splitdrive)
286
    def splitdrive(self):
287
        drive, pth = os.path.splitdrive(self)
288
        return drive, Path(pth)
289
290
    @doc(os.path.splitext)
291
    def splitext(self):
292
        return os.path.splitext(self)
293
294
    @property
295
    def ext(self):
296
        return self.splitext()[1]
297
298
    if hasattr(os.path, 'splitunc'):  # pragma: nocover
299
        @doc(os.path.splitunc)
300
        def splitunc(self):
301
            return os.path.splitunc(self)
302
303
    @doc(os.access)
304
    def access(self, *args, **kw):
305
        return os.access(self, *args, **kw)
306
307
    @doc(os.chdir)
308
    def chdir(self):
309
        return os.chdir(self)
310
311
    @contextmanager
312
    def cd(self):
313
        cwd = os.getcwd()
314
        try:
315
            self.chdir()
316
            yield self
317
        finally:
318
            os.chdir(cwd)
319
320
    @doc(os.chmod)
321
    def chmod(self, *args, **kw):
322
        return os.chmod(self, *args, **kw)
323
324
    def list(self, filterfn=lambda x: True):
325
        """Return all direct descendands of directory `self` for which
326
           `filterfn` returns True.
327
        """
328
        return [self / p for p in self.listdir() if filterfn(self / p)]
329
330
    @doc(os.listdir)
331
    def listdir(self):
332
        return [Path(p) for p in os.listdir(self)]
333
334
    def subdirs(self):
335
        """Return all direct sub-directories.
336
        """
337
        return self.list(lambda p: p.isdir())
338
339
    def files(self):
340
        """Return all files in directory.
341
        """
342
        return self.list(lambda p: p.isfile())
343
344
    @doc(os.lstat)
345
    def lstat(self):
346
        return os.lstat(self)
347
348
    @doc(os.makedirs)
349
    def makedirs(self, path=None, mode=0o777):
350
        pth = os.path.join(self, path) if path else self
351
        try:
352
            os.makedirs(pth, mode)
353
        except OSError:
354
            pass
355
        return Path(pth)
356
357
    @doc(os.mkdir)
358
    def mkdir(self, path, mode=0o777):
359
        pth = os.path.join(self, path)
360
        os.mkdir(pth, mode)
361
        return Path(pth)
362
363
    @doc(os.remove)
364
    def remove(self):
365
        return os.remove(self)
366
367
    def rm(self, fname=None):
368
        """Remove a file, don't raise exception if file does not exist.
369
        """
370
        if fname is not None:
371
            return (self / fname).rm()
372
        try:
373
            self.remove()
374
        except OSError:
375
            pass
376
377
    @doc(os.removedirs)
378
    def removedirs(self):
379
        return os.removedirs(self)
380
381
    @doc(os.rename)
382
    def rename(self, *args, **kw):
383
        return os.rename(self, *args, **kw)
384
385
    @doc(os.renames)
386
    def renames(self, *args, **kw):
387
        return os.renames(self, *args, **kw)
388
389
    @doc(os.rmdir)
390
    def rmdir(self):
391
        return os.rmdir(self)
392
393
    if hasattr(os, 'startfile'):  # pragma: nocover
394
        @doc(os.startfile)
395
        def startfile(self, *args, **kw):
396
            return os.startfile(self, *args, **kw)
397
398
    @doc(os.stat)
399
    def stat(self, *args, **kw):
400
        return os.stat(self, *args, **kw)
401
402
    @doc(os.utime)
403
    def utime(self, time=None):
404
        os.utime(self, time)
405
        return self.stat()
406
407
    def __add__(self, other):
408
        return Path(str(self) + str(other))
409
410
411
@contextmanager
412
def cd(pth):
413
    cwd = os.getcwd()
414
    try:
415
        os.chdir(pth)
416
        yield
417
    finally:
418
        os.chdir(cwd)
419