]> arthur.barton.de Git - bup.git/blob - lib/bup/compat.py
7527ae7695590fdae40f2e723c1df2a081c31b9c
[bup.git] / lib / bup / compat.py
1
2 from __future__ import absolute_import, print_function
3 from binascii import hexlify
4 from traceback import print_exception
5 import os, sys
6
7 # Please see CODINGSTYLE for important exception handling guidelines
8 # and the rationale behind add_ex_tb(), add_ex_ctx(), etc.
9
10 py_maj = sys.version_info[0]
11 py3 = py_maj >= 3
12
13 if py3:
14
15     # pylint: disable=unused-import
16     from contextlib import nullcontext
17     from mmap import mmap
18     from os import environb as environ
19     from os import fsdecode, fsencode
20     from shlex import quote
21     # pylint: disable=undefined-variable
22     # (for python2 looking here)
23     ModuleNotFoundError = ModuleNotFoundError
24     input = input
25     range = range
26     str_type = str
27     int_types = (int,)
28
29     def hexstr(b):
30         """Return hex string (not bytes as with hexlify) representation of b."""
31         return b.hex()
32
33     def reraise(ex):
34         raise ex.with_traceback(sys.exc_info()[2])
35
36     def add_ex_tb(ex):
37         """Do nothing (already handled by Python 3 infrastructure)."""
38         return ex
39
40     def add_ex_ctx(ex, context_ex):
41         """Do nothing (already handled by Python 3 infrastructure)."""
42         return ex
43
44     class pending_raise:
45         """If rethrow is true, rethrow ex (if any), unless the body throws.
46
47         (Supports Python 2 compatibility.)
48
49         """
50         def __init__(self, ex, rethrow=True):
51             self.ex = ex
52             self.rethrow = rethrow
53         def __enter__(self):
54             return None
55         def __exit__(self, exc_type, exc_value, traceback):
56             if not exc_type and self.ex and self.rethrow:
57                 raise self.ex
58
59     def items(x):
60         return x.items()
61
62     def argv_bytes(x):
63         """Return the original bytes passed to main() for an argv argument."""
64         return fsencode(x)
65
66     def bytes_from_uint(i):
67         return bytes((i,))
68
69     def bytes_from_byte(b):  # python > 2: b[3] returns ord('x'), not b'x'
70         return bytes((b,))
71
72     byte_int = lambda x: x
73
74     def buffer(object, offset=None, size=None):
75         if size:
76             assert offset is not None
77             return memoryview(object)[offset:offset + size]
78         if offset:
79             return memoryview(object)[offset:]
80         return memoryview(object)
81
82     def getcwd():
83         return fsencode(os.getcwd())
84
85 else:  # Python 2
86
87     from contextlib import contextmanager
88     import mmap as py_mmap
89
90     ModuleNotFoundError = ImportError
91
92     def fsdecode(x):
93         return x
94
95     def fsencode(x):
96         return x
97
98     from pipes import quote
99     # pylint: disable=unused-import
100     from os import environ, getcwd
101
102     # pylint: disable=unused-import
103     from bup.py2raise import reraise
104
105     @contextmanager
106     def nullcontext(enter_result=None):
107         yield enter_result
108
109     # on py3 this causes errors, obviously
110     # pylint: disable=undefined-variable
111     input = raw_input
112     # pylint: disable=undefined-variable
113     range = xrange
114     # pylint: disable=undefined-variable
115     str_type = basestring
116     # pylint: disable=undefined-variable
117     int_types = (int, long)
118
119     hexstr = hexlify
120
121     def add_ex_tb(ex):
122         """Add a traceback to ex if it doesn't already have one.  Return ex.
123
124         """
125         if not getattr(ex, '__traceback__', None):
126             ex.__traceback__ = sys.exc_info()[2]
127         return ex
128
129     def add_ex_ctx(ex, context_ex):
130         """Make context_ex the __context__ of ex (unless it already has one).
131         Return ex.
132
133         """
134         if context_ex:
135             if not getattr(ex, '__context__', None):
136                 ex.__context__ = context_ex
137         return ex
138
139     class pending_raise:
140         """If rethrow is true, rethrow ex (if any), unless the body throws.
141
142         If the body does throw, make any provided ex the __context__
143         of the newer exception (assuming there's no existing
144         __context__).  Ensure the exceptions have __tracebacks__.
145         (Supports Python 2 compatibility.)
146
147         """
148         def __init__(self, ex, rethrow=True):
149             self.ex = ex
150             self.rethrow = rethrow
151         def __enter__(self):
152             if self.ex:
153                 add_ex_tb(self.ex)
154         def __exit__(self, exc_type, exc_value, traceback):
155             if exc_value:
156                 if self.ex:
157                     add_ex_tb(exc_value)
158                     add_ex_ctx(exc_value, self.ex)
159                 return
160             if self.rethrow and self.ex:
161                 raise self.ex
162
163     def dump_traceback(ex):
164         stack = [ex]
165         next_ex = getattr(ex, '__context__', None)
166         while next_ex:
167             stack.append(next_ex)
168             next_ex = getattr(next_ex, '__context__', None)
169         stack = reversed(stack)
170         ex = next(stack)
171         tb = getattr(ex, '__traceback__', None)
172         print_exception(type(ex), ex, tb)
173         for ex in stack:
174             print('\nDuring handling of the above exception, another exception occurred:\n',
175                   file=sys.stderr)
176             tb = getattr(ex, '__traceback__', None)
177             print_exception(type(ex), ex, tb)
178
179     def items(x):
180         return x.iteritems()
181
182     def argv_bytes(x):
183         """Return the original bytes passed to main() for an argv argument."""
184         return x
185
186     bytes_from_uint = chr
187
188     def bytes_from_byte(b):
189         return b
190
191     byte_int = ord
192
193     buffer = buffer
194
195     assert not hasattr(py_mmap.mmap, '__enter__')
196     assert not hasattr(py_mmap.mmap, '__exit__')
197
198     class mmap(py_mmap.mmap):
199         def __enter__(self):
200             return self
201         def __exit__(self, type, value, traceback):
202             with pending_raise(value, rethrow=False):
203                 self.close()
204
205 try:
206     import bup_main
207 except ModuleNotFoundError:
208     bup_main = None
209
210 if bup_main:
211     def get_argvb():
212         "Return a new list containing the current process argv bytes."
213         return bup_main.argv()
214     if py3:
215         def get_argv():
216             "Return a new list containing the current process argv strings."
217             return [x.decode(errors='surrogateescape') for x in bup_main.argv()]
218     else:
219         def get_argv():
220             return bup_main.argv()
221 else:
222     def get_argvb():
223         raise Exception('get_argvb requires the bup_main module');
224     def get_argv():
225         raise Exception('get_argv requires the bup_main module');
226
227 def wrap_main(main):
228     """Run main() and raise a SystemExit with the return value if it
229     returns, pass along any SystemExit it raises, convert
230     KeyboardInterrupts into exit(130), and print a Python 3 style
231     contextual backtrace for other exceptions in both Python 2 and
232     3)."""
233     try:
234         sys.exit(main())
235     except KeyboardInterrupt as ex:
236         sys.exit(130)
237     except SystemExit as ex:
238         raise
239     except BaseException as ex:
240         if py3:
241             raise
242         add_ex_tb(ex)
243         dump_traceback(ex)
244         sys.exit(1)
245
246
247 # Excepting wrap_main() in the traceback, these should produce similar output:
248 #   python2 lib/bup/compat.py
249 #   python3 lib/bup/compat.py
250 # i.e.:
251 #   diff -u <(python2 lib/bup/compat.py 2>&1) <(python3 lib/bup/compat.py 2>&1)
252 #
253 # Though the python3 output for 'second' will include a stacktrace
254 # starting from wrap_main, rather than from outer().
255
256 if __name__ == '__main__':
257
258     def inner():
259         raise Exception('first')
260
261     def outer():
262         try:
263             inner()
264         except Exception as ex:
265             add_ex_tb(ex)
266             try:
267                 raise Exception('second')
268             except Exception as ex2:
269                 raise add_ex_ctx(add_ex_tb(ex2), ex)
270
271     wrap_main(outer)