Completed
Push — master ( 98e1d4...d1821a )
by Satoru
01:27
created

_norm_paths_itr()   B

Complexity

Conditions 5

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 5
c 1
b 0
f 1
dl 0
loc 12
rs 8.5454
1
#
2
# Copyright (C) 2012 - 2017 Satoru SATOH <ssato @ redhat.com>
3
# License: MIT
4
#
5
"""Misc utility routines for anyconfig module.
6
"""
7
from __future__ import absolute_import
8
9
import collections
10
import glob
11
import os.path
12
import types
13
14
import anyconfig.compat
15
16
17
def get_file_extension(file_path):
18
    """
19
    >>> get_file_extension("/a/b/c")
20
    ''
21
    >>> get_file_extension("/a/b.txt")
22
    'txt'
23
    >>> get_file_extension("/a/b/c.tar.xz")
24
    'xz'
25
    """
26
    _ext = os.path.splitext(file_path)[-1]
27
    if _ext:
28
        return _ext[1:] if _ext.startswith('.') else _ext
29
    else:
30
        return ""
31
32
33
def sglob(files_pattern):
34
    """
35
    glob.glob alternative of which results sorted always.
36
    """
37
    return sorted(glob.glob(files_pattern))
38
39
40
def is_iterable(obj):
41
    """
42
    >>> is_iterable([])
43
    True
44
    >>> is_iterable(())
45
    True
46
    >>> is_iterable([x for x in range(10)])
47
    True
48
    >>> is_iterable((1, 2, 3))
49
    True
50
    >>> g = (x for x in range(10))
51
    >>> is_iterable(g)
52
    True
53
    >>> is_iterable("abc")
54
    False
55
    >>> is_iterable(0)
56
    False
57
    >>> is_iterable({})
58
    False
59
    """
60
    return isinstance(obj, (list, tuple, types.GeneratorType)) or \
61
        (not isinstance(obj, (int, str, dict)) and
62
         bool(getattr(obj, "next", False)))
63
64
65
def concat(xss):
66
    """
67
    Concatenates a list of lists.
68
69
    >>> concat([[]])
70
    []
71
    >>> concat((()))
72
    []
73
    >>> concat([[1,2,3],[4,5]])
74
    [1, 2, 3, 4, 5]
75
    >>> concat([[1,2,3],[4,5,[6,7]]])
76
    [1, 2, 3, 4, 5, [6, 7]]
77
    >>> concat(((1,2,3),(4,5,[6,7])))
78
    [1, 2, 3, 4, 5, [6, 7]]
79
    >>> concat(((1,2,3),(4,5,[6,7])))
80
    [1, 2, 3, 4, 5, [6, 7]]
81
    >>> concat((i, i*2) for i in range(3))
82
    [0, 0, 1, 2, 2, 4]
83
    """
84
    return list(anyconfig.compat.from_iterable(xs for xs in xss))
85
86
87
def normpath(path):
88
    """Normalize path.
89
90
    - eliminating double slashes, etc. (os.path.normpath)
91
    - ensure paths contain ~[user]/ expanded.
92
93
    :param path: Path string :: str
94
    """
95
    return os.path.normpath(os.path.expanduser(path) if '~' in path else path)
96
97
98
def is_path(path_or_stream):
99
    """
100
    Is given object `path_or_stream` a file path?
101
102
    :param path_or_stream: file path or stream, file/file-like object
103
    :return: True if `path_or_stream` is a file path
104
    """
105
    return isinstance(path_or_stream, anyconfig.compat.STR_TYPES)
106
107
108
def get_path_from_stream(maybe_stream):
109
    """
110
    Try to get file path from given stream `stream`.
111
112
    :param maybe_stream: A file or file-like object
113
    :return: Path of given file or file-like object or None
114
115
    >>> __file__ == get_path_from_stream(__file__)
116
    True
117
    >>> __file__ == get_path_from_stream(open(__file__, 'r'))
118
    True
119
    >>> strm = anyconfig.compat.StringIO()
120
    >>> get_path_from_stream(strm) is None
121
    True
122
    """
123
    if is_path(maybe_stream):
124
        return maybe_stream  # It's path.
125
126
    maybe_path = getattr(maybe_stream, "name", None)
127
    if maybe_path is not None:
128
        maybe_path = os.path.abspath(maybe_path)
129
130
    return maybe_path
131
132
133
def _try_to_get_extension(path_or_strm):
134
    """
135
    Try to get file extension from given path or file object.
136
137
    :return: File extension or None
138
    """
139
    path = get_path_from_stream(path_or_strm)
140
    if path is None:
141
        return None
142
143
    return get_file_extension(path) or None
144
145
146
def are_same_file_types(paths):
147
    """
148
    Are given (maybe) file paths same type (extension) ?
149
150
    :param paths: A list of file path or file(-like) objects
151
152
    >>> are_same_file_types([])
153
    False
154
    >>> are_same_file_types(["a.conf"])
155
    True
156
    >>> are_same_file_types(["a.conf", "b.conf"])
157
    True
158
    >>> are_same_file_types(["a.yml", "b.yml"])
159
    True
160
    >>> are_same_file_types(["a.yml", "b.json"])
161
    False
162
    >>> strm = anyconfig.compat.StringIO()
163
    >>> are_same_file_types(["a.yml", "b.yml", strm])
164
    False
165
    """
166
    if not paths:
167
        return False
168
169
    ext = _try_to_get_extension(paths[0])
170
    if ext is None:
171
        return False
172
173
    return all(_try_to_get_extension(p) == ext for p in paths[1:])
174
175
176
def _norm_paths_itr(paths, marker='*'):
177
    """Iterator version of :func:`norm_paths`.
178
    """
179
    for path in paths:
180
        if is_path(path):
181
            if marker in path:  # glob path pattern
182
                for ppath in sglob(path):
183
                    yield ppath
184
            else:
185
                yield path  # a simple file path
186
        else:  # A file or file-like object
187
            yield path
188
189
190
def norm_paths(paths, marker='*'):
191
    """
192
    :param paths:
193
        A glob path pattern string, or a list consists of path strings or glob
194
        path pattern strings or file objects
195
    :param marker: Glob marker character or string, e.g. '*'
196
    :return: List of path strings
197
198
    >>> norm_paths([])
199
    []
200
    >>> norm_paths("/usr/lib/a/b.conf /etc/a/b.conf /run/a/b.conf".split())
201
    ['/usr/lib/a/b.conf', '/etc/a/b.conf', '/run/a/b.conf']
202
    >>> paths_s = os.path.join(os.path.dirname(__file__), "u*.py")
203
    >>> ref = sglob(paths_s)
204
    >>> assert norm_paths(paths_s) == ref
205
    >>> ref = ["/etc/a.conf"] + ref
206
    >>> assert norm_paths(["/etc/a.conf", paths_s]) == ref
207
    >>> strm = anyconfig.compat.StringIO()
208
    >>> assert norm_paths(["/etc/a.conf", strm]) == ["/etc/a.conf", strm]
209
    """
210
    if is_path(paths) and marker in paths:
211
        return sglob(paths)
212
213
    return list(_norm_paths_itr(paths, marker=marker))
214
215
216
# pylint: disable=unused-argument
217
def noop(val, *args, **kwargs):
218
    """A function does nothing.
219
220
    >>> noop(1)
221
    1
222
    """
223
    # It means nothing but can suppress 'Unused argument' pylint warns.
224
    # (val, args, kwargs)[0]
225
    return val
226
227
228
_LIST_LIKE_TYPES = (collections.Iterable, collections.Sequence)
229
230
231
def is_dict_like(obj):
232
    """
233
    :param obj: Any object behaves like a dict.
234
235
    >>> is_dict_like("a string")
236
    False
237
    >>> is_dict_like({})
238
    True
239
    >>> is_dict_like(anyconfig.compat.OrderedDict((('a', 1), ('b', 2))))
240
    True
241
    """
242
    return isinstance(obj, (dict, collections.Mapping))  # any others?
243
244
245
def is_namedtuple(obj):
246
    """
247
    >>> p0 = collections.namedtuple("Point", "x y")(1, 2)
248
    >>> is_namedtuple(p0)
249
    True
250
    >>> is_namedtuple(tuple(p0))
251
    False
252
    """
253
    return isinstance(obj, tuple) and hasattr(obj, "_asdict")
254
255
256
def is_list_like(obj):
257
    """
258
    >>> is_list_like([])
259
    True
260
    >>> is_list_like(())
261
    True
262
    >>> is_list_like([x for x in range(10)])
263
    True
264
    >>> is_list_like((1, 2, 3))
265
    True
266
    >>> g = (x for x in range(10))
267
    >>> is_list_like(g)
268
    True
269
    >>> is_list_like("abc")
270
    False
271
    >>> is_list_like(0)
272
    False
273
    >>> is_list_like({})
274
    False
275
    """
276
    return isinstance(obj, _LIST_LIKE_TYPES) and \
277
        not (isinstance(obj, anyconfig.compat.STR_TYPES) or is_dict_like(obj))
278
279
280
def filter_options(keys, options):
281
    """
282
    Filter `options` with given `keys`.
283
284
    :param keys: key names of optional keyword arguments
285
    :param options: optional keyword arguments to filter with `keys`
286
287
    >>> filter_options(("aaa", ), dict(aaa=1, bbb=2))
288
    {'aaa': 1}
289
    >>> filter_options(("aaa", ), dict(bbb=2))
290
    {}
291
    """
292
    return dict((k, options[k]) for k in keys if k in options)
293
294
# vim:sw=4:ts=4:et:
295