Completed
Push — master ( 5f68ae...c3dae1 )
by Bjorn
01:12
created

Path.glob()   F

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