Completed
Push — master ( 78fb32...3a5241 )
by Alexandre M.
38s queued 21s
created

_touch()   B

Complexity

Conditions 2

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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