1# -*- coding: utf-8 -*-
2import inspect
3import os
4import re
5import sys
6from contextlib import contextmanager
7
8IS_WIN32 = sys.platform == "win32"
9
10
11def _setdoc(super):  # @ReservedAssignment
12    """This inherits the docs on the current class. Not really needed for Python 3.5,
13    due to new behavoir of inspect.getdoc, but still doesn't hurt."""
14
15    def deco(func):
16        func.__doc__ = getattr(getattr(super, func.__name__, None), "__doc__", None)
17        return func
18
19    return deco
20
21
22class ProcInfo(object):
23    def __init__(self, pid, uid, stat, args):
24        self.pid = pid
25        self.uid = uid
26        self.stat = stat
27        self.args = args
28
29    def __repr__(self):
30        return "ProcInfo({!r}, {!r}, {!r}, {!r})".format(
31            self.pid, self.uid, self.stat, self.args
32        )
33
34
35class six(object):
36    """
37    A light-weight version of six (which works on IronPython)
38    """
39
40    PY3 = sys.version_info[0] >= 3
41    if sys.version_info >= (3, 4):
42        from abc import ABC
43    else:
44        from abc import ABCMeta
45
46        ABC = ABCMeta("ABC", (object,), {"__module__": __name__, "__slots__": ()})
47
48    # Be sure to use named-tuple access, so that usage is not affected
49    try:
50        getfullargspec = staticmethod(inspect.getfullargspec)
51    except AttributeError:
52        getfullargspec = staticmethod(
53            inspect.getargspec
54        )  # extra fields will not be available
55
56    if PY3:
57        integer_types = (int,)
58        string_types = (str,)
59        MAXSIZE = sys.maxsize
60        ascii = ascii  # @UndefinedVariable
61        bytes = bytes  # @ReservedAssignment
62        unicode_type = str
63
64        @staticmethod
65        def b(s):
66            return s.encode("latin-1", "replace")
67
68        @staticmethod
69        def u(s):
70            return s
71
72        @staticmethod
73        def get_method_function(m):
74            return m.__func__
75
76    else:
77        integer_types = (int, long)
78        string_types = (str, unicode)
79        MAXSIZE = getattr(sys, "maxsize", sys.maxint)
80        ascii = repr  # @ReservedAssignment
81        bytes = str  # @ReservedAssignment
82        unicode_type = unicode
83
84        @staticmethod
85        def b(st):
86            return st
87
88        @staticmethod
89        def u(s):
90            return s.decode("unicode-escape")
91
92        @staticmethod
93        def get_method_function(m):
94            return m.im_func
95
96    str = unicode_type
97
98
99# Try/except fails because io has the wrong StringIO in Python2
100# You'll get str/unicode errors
101if sys.version_info >= (3, 0):
102    from io import StringIO
103else:
104    from StringIO import StringIO
105
106if sys.version_info >= (3,):
107    from glob import escape as glob_escape
108else:
109    _magic_check = re.compile(u"([*?[])")
110    _magic_check_bytes = re.compile(b"([*?[])")
111
112    def glob_escape(pathname):
113        drive, pathname = os.path.splitdrive(pathname)
114        if isinstance(pathname, str):
115            pathname = _magic_check_bytes.sub(r"[\1]", pathname)
116        else:
117            pathname = _magic_check.sub(u"[\\1]", pathname)
118        return drive + pathname
119
120
121@contextmanager
122def captured_stdout(stdin=""):
123    """
124    Captures stdout (similar to the redirect_stdout in Python 3.4+, but with slightly different arguments)
125    """
126    prevstdin = sys.stdin
127    prevstdout = sys.stdout
128    sys.stdin = StringIO(six.u(stdin))
129    sys.stdout = StringIO()
130    try:
131        yield sys.stdout
132    finally:
133        sys.stdin = prevstdin
134        sys.stdout = prevstdout
135
136
137class StaticProperty(object):
138    """This acts like a static property, allowing access via class or object.
139    This is a non-data descriptor."""
140
141    def __init__(self, function):
142        self._function = function
143        self.__doc__ = function.__doc__
144
145    def __get__(self, obj, klass=None):
146        return self._function()
147
148
149def getdoc(object):
150    """
151    This gets a docstring if available, and cleans it, but does not look up docs in
152    inheritance tree (Pre 3.5 behavior of ``inspect.getdoc``).
153    """
154    try:
155        doc = object.__doc__
156    except AttributeError:
157        return None
158    if not isinstance(doc, str):
159        return None
160    return inspect.cleandoc(doc)
161
162
163def read_fd_decode_safely(fd, size=4096):
164    """
165    This reads a utf-8 file descriptor and returns a chunck, growing up to
166    three bytes if needed to decode the character at the end.
167
168    Returns the data and the decoded text.
169    """
170    data = os.read(fd.fileno(), size)
171    for i in range(4):
172        try:
173            return data, data.decode("utf-8")
174        except UnicodeDecodeError as e:
175            if e.reason != "unexpected end of data":
176                raise
177            if i == 3:
178                raise
179            data += os.read(fd.fileno(), 1)
180