Completed
Push — master ( 88e002...ac3c9d )
by Alexandre M.
01:01
created

hansel._touch()   A

Complexity

Conditions 2

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 2
dl 0
loc 23
rs 9.0856
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 _makedirs(dirpath, exist_ok=True):
246
    """ Is a replacement for os.makedirs for Python 2.7, with the exist_ok argument."""
247
    if op.exists(dirpath):
248
        if not exist_ok:
249
            raise IOError("Folder {} already exists.".format(dirpath))
250
        else:
251
            return dirpath
252
253
    return _make_new_dirs(dirpath)
254
255
256
def _make_new_dirs(dirpath):
257
    """ Call os.makedirs(dirpath), will return dirpath if no exception is raised."""
258
    try:
259
        os.makedirs(dirpath)
260
    except:
261
        raise
262
    else:
263
        return dirpath
264
265
266
def _touch(crumb_path, exist_ok=True):
267
    """ Create a leaf directory and all intermediate ones
268
    using the non crumbed part of `crumb_path`.
269
    If the target directory already exists, raise an IOError
270
    if exist_ok is False. Otherwise no exception is raised.
271
    Parameters
272
    ----------
273
    crumb_path: str
274
275
    exist_ok: bool
276
        Default = True
277
278
    Returns
279
    -------
280
    nupath: str
281
        The new path created.
282
    """
283
    if has_crumbs(crumb_path):
284
        nupath = _split(crumb_path)[0]
285
    else:
286
        nupath = crumb_path
287
288
    return _makedirs(nupath, exist_ok=exist_ok)
289
290
291
def _split_exists(crumb_path):
292
    """ Return True if the part without crumb arguments of `crumb_path`
293
    is an existing path or a symlink, False otherwise.
294
    Returns
295
    -------
296
    exists: bool
297
    """
298
    if has_crumbs(crumb_path):
299
        rpath = _split(crumb_path)[0]
300
    else:
301
        rpath = str(crumb_path)
302
303
    return op.exists(rpath) or op.islink(rpath)
304
305
306
def _check_is_subset(list1, list2):
307
    """ Raise an error if `list1` is not a subset of `list2`."""
308
    if not set(list1).issubset(set(list2)):
309
        raise KeyError('The `list1` argument should be a subset of `list2`, '
310
                       'got {} and {}.'.format(list1, list2))
311
312
313
def deprecated(replacement=None):
314
    """A decorator which can be used to mark functions as deprecated.
315
    replacement is a callable that will be called with the same args
316
    as the decorated function.
317
318
    >>> @deprecated()
319
    ... def foo(x):
320
    ...     return x
321
    ...
322
    >>> ret = foo(1)
323
    DeprecationWarning: foo is deprecated
324
    >>> ret
325
    1
326
    >>>
327
    >>>
328
    >>> def newfun(x):
329
    ...     return 0
330
    ...
331
    >>> @deprecated(newfun)
332
    ... def foo(x):
333
    ...     return x
334
    ...
335
    >>> ret = foo(1)
336
    DeprecationWarning: foo is deprecated; use newfun instead
337
    >>> ret
338
    0
339
    >>>
340
    """
341
    def outer(fun):
342
        msg = "psutil.%s is deprecated" % fun.__name__
343
        if replacement is not None:
344
            msg += "; use %s instead" % replacement
345
        if fun.__doc__ is None:
346
            fun.__doc__ = msg
347
348
        @functools.wraps(fun)
349
        def inner(*args, **kwargs):
350
            warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
351
            return fun(*args, **kwargs)
352
353
        return inner
354
    return outer
355