Completed
Push — dev-4.1-unstable ( 426fcf...0bd0f2 )
by Felipe A.
01:04
created

usedoc()   B

Complexity

Conditions 2

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
1
#!/usr/bin/env python
2
# -*- coding: UTF-8 -*-
3
4
import os
5
import os.path
6
import sys
7
import itertools
8
9
import warnings
10
import functools
11
12
FS_ENCODING = sys.getfilesystemencoding()
13
PY_LEGACY = sys.version_info < (3, )
14
ENV_PATH = []  # populated later
15
TRUE_VALUES = frozenset(('true', 'yes', '1', 'enable', 'enabled', True, 1))
16
17
try:
18
    from scandir import scandir, walk
19
except ImportError:
20
    if not hasattr(os, 'scandir'):
21
        raise
22
    scandir = os.scandir
23
    walk = os.walk
24
25
26
def isexec(path):
27
    '''
28
    Check if given path points to an executable file.
29
30
    :param path: file path
31
    :type path: str
32
    :return: True if executable, False otherwise
33
    :rtype: bool
34
    '''
35
    return os.path.isfile(path) and os.access(path, os.X_OK)
36
37
38
def which(name,
39
          env_path=ENV_PATH,
40
          is_executable_fnc=isexec,
41
          path_join_fnc=os.path.join):
42
    '''
43
    Get command absolute path.
44
45
    :param name: name of executable command
46
    :type name: str
47
    :param env_path: OS environment executable paths, defaults to autodetected
48
    :type env_path: list of str
49
    :param is_executable_fnc: callable will be used to detect if path is
50
                              executable, defaults to `isexec`
51
    :type is_executable_fnc: Callable
52
    :param path_join_fnc: callable will be used to join path components
53
    :type path_join_fnc: Callable
54
    :return: absolute path
55
    :rtype: str or None
56
    '''
57
    for path in env_path:
58
        exe_file = path_join_fnc(path, name)
59
        if is_executable_fnc(exe_file):
60
            return exe_file
61
    return None
62
63
64
def fsdecode(path, os_name=os.name, fs_encoding=FS_ENCODING, errors=None):
65
    '''
66
    Decode given path.
67
68
    :param path: path will be decoded if using bytes
69
    :type path: bytes or str
70
    :param os_name: operative system name, defaults to os.name
71
    :type os_name: str
72
    :param fs_encoding: current filesystem encoding, defaults to autodetected
73
    :type fs_encoding: str
74
    :return: decoded path
75
    :rtype: str
76
    '''
77
    if not isinstance(path, bytes):
78
        return path
79
    if not errors:
80
        use_strict = PY_LEGACY or os_name == 'nt'
81
        errors = 'strict' if use_strict else 'surrogateescape'
82
    return path.decode(fs_encoding, errors=errors)
83
84
85
def fsencode(path, os_name=os.name, fs_encoding=FS_ENCODING, errors=None):
86
    '''
87
    Encode given path.
88
89
    :param path: path will be encoded if not using bytes
90
    :type path: bytes or str
91
    :param os_name: operative system name, defaults to os.name
92
    :type os_name: str
93
    :param fs_encoding: current filesystem encoding, defaults to autodetected
94
    :type fs_encoding: str
95
    :return: encoded path
96
    :rtype: bytes
97
    '''
98
    if isinstance(path, bytes):
99
        return path
100
    if not errors:
101
        use_strict = PY_LEGACY or os_name == 'nt'
102
        errors = 'strict' if use_strict else 'surrogateescape'
103
    return path.encode(fs_encoding, errors=errors)
104
105
106
def getcwd(fs_encoding=FS_ENCODING, cwd_fnc=os.getcwd):
107
    '''
108
    Get current work directory's absolute path.
109
    Like os.getcwd but garanteed to return an unicode-str object.
110
111
    :param fs_encoding: filesystem encoding, defaults to autodetected
112
    :type fs_encoding: str
113
    :param cwd_fnc: callable used to get the path, defaults to os.getcwd
114
    :type cwd_fnc: Callable
115
    :return: path
116
    :rtype: str
117
    '''
118
    path = cwd_fnc()
119
    if isinstance(path, bytes):
120
        path = fsdecode(path, fs_encoding=fs_encoding)
121
    return os.path.abspath(path)
122
123
124
def getdebug(environ=os.environ, true_values=TRUE_VALUES):
125
    '''
126
    Get if app is expected to be ran in debug mode looking at environment
127
    variables.
128
129
    :param environ: environment dict-like object
130
    :type environ: collections.abc.Mapping
131
    :returns: True if debug contains a true-like string, False otherwise
132
    :rtype: bool
133
    '''
134
    return environ.get('DEBUG', '').lower() in true_values
135
136
137
def deprecated(func_or_text, environ=os.environ):
138
    '''
139
    Decorator used to mark functions as deprecated. It will result in a
140
    warning being emmitted hen the function is called.
141
142
    Usage:
143
144
    >>> @deprecated
145
    ... def fnc():
146
    ...     pass
147
148
    Usage (custom message):
149
150
    >>> @deprecated('This is deprecated')
151
    ... def fnc():
152
    ...     pass
153
154
    :param func_or_text: message or callable to decorate
155
    :type func_or_text: callable
156
    :param environ: optional environment mapping
157
    :type environ: collections.abc.Mapping
158
    :returns: nested decorator or new decorated function (depending on params)
159
    :rtype: callable
160
    '''
161
    def inner(func):
162
        message = (
163
            'Deprecated function {}.'.format(func.__name__)
164
            if callable(func_or_text) else
165
            func_or_text
166
            )
167
168
        @functools.wraps(func)
169
        def new_func(*args, **kwargs):
170
            with warnings.catch_warnings():
171
                if getdebug(environ):
172
                    warnings.simplefilter('always', DeprecationWarning)
173
                warnings.warn(message, category=DeprecationWarning,
174
                              stacklevel=3)
175
            return func(*args, **kwargs)
176
        return new_func
177
    return inner(func_or_text) if callable(func_or_text) else inner
178
179
180
def usedoc(other):
181
    '''
182
    Decorator which copies __doc__ of given object into decorated one.
183
184
    Usage:
185
186
    >>> def fnc1():
187
    ...     """docstring"""
188
    ...     pass
189
    >>> @usedoc(fnc1)
190
    ... def fnc2():
191
    ...     pass
192
    >>> fnc2.__doc__
193
    'docstring'collections.abc.D
194
195
    :param other: anything with a __doc__ attribute
196
    :type other: any
197
    :returns: decorator function
198
    :rtype: callable
199
    '''
200
    def inner(fnc):
201
        fnc.__doc__ = fnc.__doc__ or getattr(other, '__doc__')
202
        return fnc
203
    return inner
204
205
206
ENV_PATH[:] = (
207
  fsdecode(path.strip('"'))
208
  for path in os.environ['PATH'].split(os.pathsep)
209
  )
210
211
if PY_LEGACY:
212
    FileNotFoundError = type('FileNotFoundError', (OSError,), {})
213
    range = xrange  # noqa
214
    filter = itertools.ifilter
215
    basestring = basestring
216
    unicode = unicode
217
else:
218
    FileNotFoundError = FileNotFoundError
219
    range = range
220
    filter = filter
221
    basestring = str
222
    unicode = str
223