Completed
Push — master ( 21dee9...eddb34 )
by Alexandre M.
01:06
created

hansel._build_path()   B

Complexity

Conditions 6

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 6
dl 0
loc 36
rs 7.5384
1
# -*- coding: utf-8 -*-
2
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
3
# vi: set ft=python sts=4 ts=4 sw=4 et:
4
"""
5
Crumb manipulation utilities
6
"""
7
import functools
8
import os
9
import os.path as op
10
import warnings
11
from string import Formatter
12
13
from   six import string_types
14
15
_txt_idx = 0
16
_fld_idx = 1
17
_rgx_idx = 2
18
_cnv_idx = 3
19
20
21
def _yield_items(crumb_path, index=None):
22
    """ An iterator over the items in `crumb_path` given by string.Formatter. """
23
    if index is None:
24
        return Formatter().parse(crumb_path)
25
    else:
26
        #for (literal_text, field_name, format_spec, conversion) in fmt.parse(crumb_path):
27
        # (txt, fld, fmt, conv)
28
        return (items[index] for items in Formatter().parse(crumb_path))
29
30
31
def _enum_items(crumb_path):
32
    """ An iterator over the enumerated items, i.e., (index, items) in `crumb_path`
33
     given by string.Formatter. """
34
    return ((idx, items) for idx, items in enumerate(Formatter().parse(crumb_path)))
35
36
37
def _depth_items(crumb_path, index=None):
38
    """ Return a generator with  (depth, items) in `crumb_path`. Being `depth`
39
     the place in the file path each argument is."""
40
    if index is None:
41
        index = slice(_txt_idx, _cnv_idx+1)
42
43
    depth = 0
44
    for idx, items in _enum_items(crumb_path):
45
        if items[_fld_idx]:
46
            depth += items[_txt_idx].count(op.sep)
47
            yield depth, items[index]
48
49
50
def _arg_names(crumb_path):
51
    """ Return an iterator over arg_name in crumb_path."""
52
    return _yield_items(crumb_path, _fld_idx)
53
54
55
def _depth_names(crumb_path):
56
    """ Return an iterator over (depth, arg_name)."""
57
    return _depth_items(crumb_path, _fld_idx)
58
59
60
def _depth_names_regexes(crumb_path):
61
    """ Return an iterator over (depth, (arg_name, arg_regex))."""
62
    return _depth_items(crumb_path, slice(_fld_idx, _cnv_idx))
63
64
65
def _build_path(crumb_path, arg_values, with_regex=True, regexes=None):
66
    """ Build the crumb_path with the values in arg_values.
67
    Parameters
68
    ----------
69
    crumb_path: str
70
71
    arg_values: dict[str]->str
72
        arg_name -> arg_value
73
74
    with_regex: bool
75
76
    regexes: dict[str] -> str
77
        dict[arg_name] -> regex
78
        The regexes contained here will replace or be added as a regex for
79
        the corresponding arg_name.
80
81
    Returns
82
    -------
83
    built_path: str
84
    """
85
    if regexes is None:
86
        regexes = {}
87
88
    path = ''
89
    for txt, fld, rgx, conv in _yield_items(crumb_path):
90
        path += txt
91
        if fld is None:
92
            continue
93
94
        if fld in arg_values:
95
            path += arg_values[fld]
96
        else:
97
            regex = regexes.get(fld, rgx) if with_regex else ''
98
            path += _format_arg(fld, regex=regex)
99
100
    return path
101
102
103
def is_valid(crumb_path):
104
    """ Return True if `crumb_path` is a valid Crumb value, False otherwise. """
105
    try:
106
        _ = list(_depth_names_regexes(crumb_path))
107
    except ValueError:
108
        return False
109
    else:
110
        return True
111
112
113
def _first_txt(crumb_path):
114
    """ Return the first text part without arguments in `crumb_path`. """
115
    for txt in _yield_items(crumb_path, index=_txt_idx):
116
        return txt
117
118
119
def _find_arg_depth(crumb_path, arg_name):
120
    """ Return the depth, name and regex of the argument with name `arg_name`. """
121
    for depth, (txt, fld, rgx, conv) in _depth_items(crumb_path):
122
        if fld == arg_name:
123
            return depth, fld, rgx
124
125
126
def _has_arg(crumb_path, arg_name):
127
    """ Return the True if the `arg_name` is found in `crumb_path`. """
128
    for txt, fld, rgx, conv in _yield_items(crumb_path):
129
        if fld == arg_name:
130
            return True
131
    return False
132
133
134
def _check(crumb_path):
135
    """ Raises some Errors if there is something wrong with `crumb_path`, if not the type
136
    needed or is not valid.
137
    Parameters
138
    ----------
139
    crumb_path: str
140
141
    Raises
142
    ------
143
     - ValueError if the path of the Crumb has errors using `self.is_valid`.
144
     - TypeError if the crumb_path is not a str or a Crumb.
145
    """
146
    if not isinstance(crumb_path, string_types):
147
        raise TypeError("Expected `crumb_path` to be a {}, got {}.".format(string_types, type(crumb_path)))
148
149
    if not is_valid(crumb_path):
150
        raise ValueError("The current crumb path has errors, got {}.".format(crumb_path))
151
152
    return crumb_path
153
154
155
def _get_path(crumb_path):
156
    """ Return the path string from `crumb_path`.
157
    Parameters
158
    ----------
159
    crumb_path: str or Crumb
160
161
    Returns
162
    -------
163
    path: str
164
    """
165
    if hasattr(crumb_path, '_path'):
166
        crumb_path = crumb_path._path
167
168
    if not isinstance(crumb_path, string_types):
169
        raise TypeError("Expected `crumb_path` to be a {}, got {}.".format(string_types, type(crumb_path)))
170
171
    return crumb_path
172
173
174
def _is_crumb_arg(crumb_arg):
175
    """ Returns True if `crumb_arg` is a well formed crumb argument, i.e.,
176
    is a string that starts with `start_sym` and ends with `end_sym`. False otherwise."""
177
    if not isinstance(crumb_arg, string_types):
178
        return False
179
    start_sym, end_sym = ('{', '}')
180
    return crumb_arg.startswith(start_sym) and crumb_arg.endswith(end_sym)
181
182
183
def _format_arg(arg_name, regex=''):
184
    """ Return the crumb argument for its string `format()` representation.
185
    Parameters
186
    ----------
187
    arg_name: str
188
189
    Returns
190
    -------
191
    arg_format: str
192
    """
193
    start_sym, end_sym = ('{', '}')
194
    reg_sym = ':'
195
196
    arg_fmt = start_sym + arg_name
197
    if regex:
198
        arg_fmt += reg_sym + regex
199
    arg_fmt += end_sym
200
201
    return arg_fmt
202
203
204
def has_crumbs(crumb_path):
205
    """ Return True if the `crumb_path.split(op.sep)` has item which is a crumb argument
206
    that starts with '{' and ends with '}'."""
207
    crumb_path = _get_path(crumb_path)
208
209
    splt = crumb_path.split(op.sep)
210
    for i in splt:
211
        if _is_crumb_arg(i):
212
            return True
213
214
    return False
215
216
217
def _split(crumb_path):
218
    """ Split `crumb_path` in two parts, the first is the base folder without any crumb argument
219
        and the second is the rest of `crumb_path` beginning with the first crumb argument.
220
        If `crumb_path` starts with an argument, will return ('', crumb_path).
221
    """
222
    crumb_path = _get_path(crumb_path)
223
224
    if not has_crumbs(crumb_path):
225
        return crumb_path, ''
226
227
    if not is_valid(crumb_path):
228
        raise ValueError('Crumb path {} is not valid.'.format(crumb_path))
229
230
    start_sym, end_sym = '{', '}'
231
    if crumb_path.startswith(start_sym):
232
        base = ''
233
        rest = crumb_path
234
    else:
235
        idx = crumb_path.find(start_sym)
236
        base = crumb_path[0:idx]
237
        if base.endswith(op.sep):
238
            base = base[:-1]
239
240
        rest = crumb_path[idx:]
241
242
    return base, rest
243
244
245
def _touch(crumb_path, exist_ok=True):
246
    """ Create a leaf directory and all intermediate ones
247
    using the non crumbed part of `crumb_path`.
248
    If the target directory already exists, raise an IOError
249
    if exist_ok is False. Otherwise no exception is raised.
250
    Parameters
251
    ----------
252
    crumb_path: str
253
254
    exist_ok: bool
255
        Default = True
256
257
    Returns
258
    -------
259
    nupath: str
260
        The new path created.
261
    """
262
    if has_crumbs(crumb_path):
263
        nupath = _split(crumb_path)[0]
264
    else:
265
        nupath = crumb_path
266
267
    if op.exists(nupath) and not exist_ok:
268
        raise IOError("Folder {} already exists.".format(nupath))
269
    elif op.exists(nupath) and exist_ok:
270
        return nupath
271
272
    try:
273
        os.makedirs(nupath)
274
    except:
275
        raise
276
    else:
277
        return nupath
278
279
280
def _split_exists(crumb_path):
281
    """ Return True if the part without crumb arguments of `crumb_path`
282
    is an existing path or a symlink, False otherwise.
283
    Returns
284
    -------
285
    exists: bool
286
    """
287
    if has_crumbs(crumb_path):
288
        rpath = _split(crumb_path)[0]
289
    else:
290
        rpath = str(crumb_path)
291
292
    return op.exists(rpath) or op.islink(rpath)
293
294
295
def _check_is_subset(list1, list2):
296
    """ Raise an error if `list1` is not a subset of `list2`."""
297
    if not set(list1).issubset(set(list2)):
298
        raise KeyError('The `list1` argument should be a subset of `list2`, '
299
                       'got {} and {}.'.format(list1, list2))
300
301
302
def deprecated(replacement=None):
303
    """A decorator which can be used to mark functions as deprecated.
304
    replacement is a callable that will be called with the same args
305
    as the decorated function.
306
307
    >>> @deprecated()
308
    ... def foo(x):
309
    ...     return x
310
    ...
311
    >>> ret = foo(1)
312
    DeprecationWarning: foo is deprecated
313
    >>> ret
314
    1
315
    >>>
316
    >>>
317
    >>> def newfun(x):
318
    ...     return 0
319
    ...
320
    >>> @deprecated(newfun)
321
    ... def foo(x):
322
    ...     return x
323
    ...
324
    >>> ret = foo(1)
325
    DeprecationWarning: foo is deprecated; use newfun instead
326
    >>> ret
327
    0
328
    >>>
329
    """
330
    def outer(fun):
331
        msg = "psutil.%s is deprecated" % fun.__name__
332
        if replacement is not None:
333
            msg += "; use %s instead" % replacement
334
        if fun.__doc__ is None:
335
            fun.__doc__ = msg
336
337
        @functools.wraps(fun)
338
        def inner(*args, **kwargs):
339
            warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
340
            return fun(*args, **kwargs)
341
342
        return inner
343
    return outer
344