Completed
Push — master ( 1d4b4f...bdf242 )
by Bjorn
01:04
created

Path.touch()   B

Complexity

Conditions 5

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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