Completed
Push — master ( b39d46...1d4b4f )
by Bjorn
01:05
created

Path.touch()   A

Complexity

Conditions 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 5
rs 9.4285
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
    @doc(os.utime)
71
    def touch(self, times=None):
72
        # Cf. http://stackoverflow.com/questions/1158076/implement-touch-using-python
73
        with open(self, 'a'):
74
            os.utime(self, times)
75
76
    def glob(self, pat):
77
        """`pat` can be an extended glob pattern, e.g. `'**/*.less'`
78
           This code handles negations similarly to node.js' minimatch, i.e.
79
           a leading `!` will negate the entire pattern.
80
        """
81
        r = ""
82
        negate = int(pat.startswith('!'))
83
        i = negate
84
85
        while i < len(pat):
86
            if pat[i:i + 3] == '**/':
87
                r += "(?:.*/)?"
88
                i += 3
89
            elif pat[i] == "*":
90
                r += "[^/]*"
91
                i += 1
92
            elif pat[i] == ".":
93
                r += "[.]"
94
                i += 1
95
            elif pat[i] == "?":
96
                r += "."
97
                i += 1
98
            else:
99
                r += pat[i]
100
                i += 1
101
        r += r'\Z(?ms)'
102
        # print '\n\npat', pat
103
        # print 'regex:', r
104
        # print [s.relpath(self).replace('\\', '/') for s in self]
105
        rx = re.compile(r)
106
107
        def match(d):
108
            m = rx.match(d)
109
            return not m if negate else m
110
111
        return [s for s in self if match(s.relpath(self).replace('\\', '/'))]
112
113
    @doc(os.path.abspath)
114
    def abspath(self):
115
        return Path(os.path.abspath(self))
116
    absolute = abspath  # pathlib
117
118
    def drive(self):
119
        """Return the drive of `self`.
120
        """
121
        return self.splitdrive()[0]
122
123
    def drivepath(self):
124
        """The path local to this drive (i.e. remove drive letter).
125
        """
126
        return self.splitdrive()[1]
127
128
    @doc(os.path.basename)
129
    def basename(self):
130
        return Path(os.path.basename(self))
131
132
    @doc(os.path.commonprefix)
133
    def commonprefix(self, *args):
134
        return os.path.commonprefix([str(self)] + [str(a) for a in args])
135
136
    @doc(os.path.dirname)
137
    def dirname(self):
138
        return Path(os.path.dirname(self))
139
140
    @doc(os.path.exists)
141
    def exists(self):
142
        return os.path.exists(self)
143
144
    @doc(os.path.expanduser)
145
    def expanduser(self):
146
        return Path(os.path.expanduser(self))
147
148
    @doc(os.path.expandvars)
149
    def expandvars(self):
150
        return Path(os.path.expandvars(self))
151
152
    @doc(os.path.getatime)
153
    def getatime(self):
154
        return os.path.getatime(self)
155
156
    @doc(os.path.getctime)
157
    def getctime(self):
158
        return os.path.getctime(self)
159
160
    @doc(os.path.getmtime)
161
    def getmtime(self):
162
        return os.path.getmtime(self)
163
164
    @doc(os.path.getsize)
165
    def getsize(self):
166
        return os.path.getsize(self)
167
168
    @doc(os.path.isabs)
169
    def isabs(self):
170
        return os.path.isabs(self)
171
172
    @doc(os.path.isdir)
173
    def isdir(self, *args, **kw):
174
        return os.path.isdir(self, *args, **kw)
175
176
    @doc(os.path.isfile)
177
    def isfile(self):
178
        return os.path.isfile(self)
179
180
    @doc(os.path.islink)
181
    def islink(self):
182
        return os.path.islink(self)
183
184
    @doc(os.path.ismount)
185
    def ismount(self):
186
        return os.path.ismount(self)
187
188
    @doc(os.path.join)
189
    def join(self, *args):
190
        return Path(os.path.join(self, *args))
191
192
    @doc(os.path.lexists)
193
    def lexists(self):
194
        return os.path.lexists(self)
195
196
    @doc(os.path.normcase)
197
    def normcase(self):
198
        return Path(os.path.normcase(self))
199
200
    @doc(os.path.normpath)
201
    def normpath(self):
202
        return Path(os.path.normpath(str(self)))
203
204
    @doc(os.path.realpath)
205
    def realpath(self):
206
        return Path(os.path.realpath(self))
207
208
    @doc(os.path.relpath)
209
    def relpath(self, other=""):
210
        return Path(os.path.relpath(str(self), str(other)))
211
212
    @doc(os.path.split)
213
    def split(self, sep=None, maxsplit=-1):
214
        # some heuristics to determine if this is a str.split call or
215
        # a os.split call...
216
        sval = str(self)
217
        if sep is not None or ' ' in sval:
218
            return sval.split(sep or ' ', maxsplit)
219
        return os.path.split(self)
220
221
    def parts(self):
222
        res = re.split(r"\\|/", self)
223
        if res and os.path.splitdrive(res[0]) == (res[0], ''):
224
            res[0] += os.path.sep
225
        return res
226
227
    def parent_iter(self):
228
        parts = self.abspath().normpath().normcase().parts()
229
        for i in range(1, len(parts)):
230
            yield Path(os.path.join(*parts[:-i]))
231
232
    @property
233
    def parents(self):
234
        return list(self.parent_iter())
235
236
    @property
237
    def parent(self):
238
        return self.parents[0]
239
240
    @doc(os.path.splitdrive)
241
    def splitdrive(self):
242
        drive, pth = os.path.splitdrive(self)
243
        return drive, Path(pth)
244
245
    @doc(os.path.splitext)
246
    def splitext(self):
247
        return os.path.splitext(self)
248
249
    @property
250
    def ext(self):
251
        return self.splitext()[1]
252
253
    if hasattr(os.path, 'splitunc'):  # pragma: nocover
254
        @doc(os.path.splitunc)
255
        def splitunc(self):
256
            return os.path.splitunc(self)
257
258
    @doc(os.access)
259
    def access(self, *args, **kw):
260
        return os.access(self, *args, **kw)
261
262
    @doc(os.chdir)
263
    def chdir(self):
264
        return os.chdir(self)
265
266
    @contextmanager
267
    def cd(self):
268
        cwd = os.getcwd()
269
        try:
270
            self.chdir()
271
            yield self
272
        finally:
273
            os.chdir(cwd)
274
275
    @doc(os.chmod)
276
    def chmod(self, *args, **kw):
277
        return os.chmod(self, *args, **kw)
278
279
    @doc(os.listdir)
280
    def listdir(self):
281
        return [Path(p) for p in os.listdir(self)]
282
283
    def list(self, filterfn=lambda x: True):
284
        """Return all direct descendands of directory `self` for which
285
           `filterfn` returns True.
286
        """
287
        return [self / p for p in self.listdir() if filterfn(self / p)]
288
289
    def subdirs(self):
290
        """Return all direct sub-directories.
291
        """
292
        return self.list(lambda p: p.isdir())
293
294
    def files(self):
295
        """Return all files in directory.
296
        """
297
        return self.list(lambda p: p.isfile())
298
299
    @doc(os.lstat)
300
    def lstat(self):
301
        return os.lstat(self)
302
303
    @doc(os.makedirs)
304
    def makedirs(self, path=None, mode=0777):
305
        pth = os.path.join(self, path) if path else self
306
        try:
307
            os.makedirs(pth, mode)
308
        except OSError:
309
            pass
310
        return Path(pth)
311
312
    @doc(os.mkdir)
313
    def mkdir(self, path, mode=0777):
314
        pth = os.path.join(self, path)
315
        os.mkdir(pth, mode)
316
        return Path(pth)
317
318
    @doc(os.remove)
319
    def remove(self):
320
        return os.remove(self)
321
322
    @doc(os.removedirs)
323
    def removedirs(self):
324
        return os.removedirs(self)
325
326
    @doc(os.rename)
327
    def rename(self, *args, **kw):
328
        return os.rename(self, *args, **kw)
329
330
    @doc(os.renames)
331
    def renames(self, *args, **kw):
332
        return os.renames(self, *args, **kw)
333
334
    @doc(os.rmdir)
335
    def rmdir(self):
336
        return os.rmdir(self)
337
338
    if hasattr(os, 'startfile'):  # pragma: nocover
339
        @doc(os.startfile)
340
        def startfile(self, *args, **kw):
341
            return os.startfile(self, *args, **kw)
342
343
    @doc(os.stat)
344
    def stat(self, *args, **kw):
345
        return os.stat(self, *args, **kw)
346
347
    @doc(os.utime)
348
    def utime(self, time=None):
349
        os.utime(self, time)
350
        return self.stat()
351
352
    def __add__(self, other):
353
        return Path(str(self) + str(other))
354
355
356
@contextmanager
357
def cd(pth):
358
    cwd = os.getcwd()
359
    try:
360
        os.chdir(pth)
361
        yield
362
    finally:
363
        os.chdir(cwd)
364