-
Notifications
You must be signed in to change notification settings - Fork 3
/
customize_sys_hooks.py
188 lines (151 loc) · 7.77 KB
/
customize_sys_hooks.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
"""Register a custom sys.displayhook and sys.excepthook.
Both of these use custom coloring. The default colors are based on the default theme, if you use something different the colors will not match your theme and may not look nice. If you're using a dark theme, you won't be able to read anything.
Features of the displayhook:
* Basic IPython-style output history. There is a global list named Out (technically added to the builtins/__builtin__ module) which contains the results of all expressions run in the interactive console.
* The result lines show at which position in the Out list the result can be found, for later reference.
* Like the default displayhook, expressions that return None are ignored - nothing is printed, and the value of _ is not changed. This can be changed so that None is displayed and stored in _ and Out, by uncommmenting a line in the displayhook definition.
Features of the excepthook:
* A little more whitespace to make the traceback more readable.
* The traceback uses different colors for text, file paths, line numbers, etc.
* Syntax errors use Unicode magic and color changing to mark at which character the syntax error occurred. Admittedly this does not always work well and may be more "look at my fancy graphics" than useful.
* Source file paths are shortened and do not show the long path of the app sandbox.
* Source file paths are tappable links that open in the Pythonista editor. This uses the normal pythonista:// URL scheme, which means that files are *not* opened in a new tab. Instead they replace whatever was open in your last active tab.
* Python 3 chained exceptions *should* work. No, that cannot be backported to Python 2.
"""
from __future__ import absolute_import, division, print_function
def run():
try:
import builtins
except ImportError:
import __builtin__ as builtins
import console
import os
import sys
import traceback
try:
from urllib.parse import quote
except ImportError:
from urllib import quote
print(u"Customizing sys hooks...")
APP_GROUP_DIR = os.path.expanduser(u"~")
if os.path.basename(APP_GROUP_DIR) == u"Pythonista3":
APP_GROUP_DIR = os.path.dirname(APP_GROUP_DIR)
APP_GROUP_DIR += os.sep
APP_DIR = u"" + os.path.dirname(os.path.dirname(sys.executable)) + os.sep
REMOVE_PREFIXES = (APP_GROUP_DIR, APP_DIR)
DOCUMENTS = os.path.expanduser("~/Documents")
def write_filename(path):
if path.startswith(u"<") and path.endswith(u">"):
print(path, end=u"")
else:
short_path = path
for prefix in REMOVE_PREFIXES:
if short_path.startswith(prefix):
short_path = path[len(prefix):]
break
console.write_link(short_path, (u"pythonista3://" if os.path.basename(sys.executable) == "Pythonista3" else u"pythonista://") + quote(os.path.relpath(path, DOCUMENTS)))
def displayhook(obj):
# Uncomment the next line if you want None to be "visible" like any other object.
#"""
if obj is None:
return
#"""
try:
builtins.Out
except AttributeError:
builtins.Out = []
builtins._ = obj
builtins.Out.append(obj)
console.set_color(0.0, 0.5, 0.0)
print(u"Out[{}]".format(len(builtins.Out)-1), end=u"")
console.set_color(0.2, 0.2, 0.2)
print(u" = ", end=u"")
console.set_color(0.33, 0.57, 1.0)
print(repr(obj))
console.set_color(0.2, 0.2, 0.2)
sys.displayhook = displayhook
def _excepthook(exc_type, exc_value, exc_traceback):
console.set_color(0.75, 0.0, 0.0)
print(u"Traceback (most recent call last):")
for filename, lineno, funcname, text in traceback.extract_tb(exc_traceback):
console.set_color(0.2, 0.2, 0.2)
print(u"\tFile ", end=u"")
console.set_color(0.81, 0.32, 0.29)
write_filename(filename)
console.set_color(0.2, 0.2, 0.2)
print(u", line ", end=u"")
console.set_color(0.15, 0.51, 0.84)
print(lineno, end=u"")
console.set_color(0.2, 0.2, 0.2)
print(u", in ", end=u"")
console.set_color(0.13, 0.46, 0.49)
print(funcname, end=u"")
console.set_color(0.2, 0.2, 0.2)
print(u":")
console.set_color(0.2, 0.2, 0.2)
if isinstance(text, bytes):
text = text.decode(u"utf-8", u"replace")
print(u"\t\t" + (text or u"# Source code unavailable"))
print()
if issubclass(exc_type, SyntaxError):
console.set_color(0.2, 0.2, 0.2)
print(u"\tFile ", end=u"")
console.set_color(0.81, 0.32, 0.29)
write_filename(exc_value.filename)
console.set_color(0.2, 0.2, 0.2)
print(u", line ", end=u"")
console.set_color(0.15, 0.51, 0.84)
print(exc_value.lineno, end=u"")
console.set_color(0.2, 0.2, 0.2)
print(u":")
if exc_value.text is None:
console.set_color(0.75, 0.0, 0.0)
print(u"\t\t# Source code unavailable")
else:
etext = exc_value.text
if isinstance(etext, bytes):
etext = etext.decode(u"utf-8", u"replace")
console.set_color(0.2, 0.2, 0.2)
print(u"\t\t" + etext[:exc_value.offset], end=u"")
console.set_color(0.75, 0.0, 0.0)
print(u"\N{COMBINING LOW LINE}" + etext[exc_value.offset:].rstrip())
console.set_color(0.43, 0.25, 0.66)
print(exc_type.__module__, end=u"")
console.set_color(0.2, 0.2, 0.2)
print(u".", end=u"")
console.set_color(0.13, 0.46, 0.49)
print(getattr(exc_type, "__qualname__", exc_type.__name__), end=u"")
msg = exc_value.msg if issubclass(exc_type, SyntaxError) else str(exc_value)
if msg:
console.set_color(0.2, 0.2, 0.2)
print(u": ", end=u"")
console.set_color(0.75, 0.0, 0.0)
print(msg, end=u"")
console.set_color(0.2, 0.2, 0.2)
print()
def excepthook(exc_type, exc_value, exc_traceback):
try:
# On Python 2, exceptions have no __cause__, __context__ or __supress_context__.
if getattr(exc_value, "__cause__", None) is not None:
excepthook(exc_value.__cause__.__class__, exc_value.__cause__, exc_value.__cause__.__traceback__)
console.set_color(0.75, 0.0, 0.0)
print()
print(u"The above exception was the direct cause of the following exception:")
print()
elif getattr(exc_value, "__context__", None) is not None and not exc_value.__suppress_context__:
excepthook(exc_value.__context__.__class__, exc_value.__context__, exc_value.__context__.__traceback__)
console.set_color(0.75, 0.0, 0.0)
print()
print(u"During handling of the above exception, another exception occurred:")
print()
_excepthook(exc_type, exc_value, exc_traceback)
except Exception as err:
traceback.print_exc()
finally:
console.set_color(0.2, 0.2, 0.2)
# sys.excepthook can't be customized, Pythonista overrides it on every script run.
# So we need to override sys.__excepthook__ instead.
sys.__excepthook__ = excepthook
print(u"Done customizing sys hooks.")
if __name__ == "__main__":
run()