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