Path.glob()   F
last analyzed

Complexity

Conditions 10

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
c 1
b 0
f 0
dl 0
loc 36
rs 3.1304

1 Method

Rating   Name   Duplication   Size   Complexity  
A Path.match() 0 3 2

How to fix   Complexity   

Complexity

Complex classes like Path.glob() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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