-
Notifications
You must be signed in to change notification settings - Fork 248
/
cs50.py
159 lines (124 loc) · 4.27 KB
/
cs50.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
from __future__ import print_function
import inspect
import logging
import os
import re
import sys
from os.path import abspath, join
from termcolor import colored
from traceback import format_exception
# Configure default logging handler and formatter
# Prevent flask, werkzeug, etc from adding default handler
logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.DEBUG)
try:
# Patch formatException
logging.root.handlers[
0
].formatter.formatException = lambda exc_info: _formatException(*exc_info)
except IndexError:
pass
# Configure cs50 logger
_logger = logging.getLogger("cs50")
_logger.setLevel(logging.DEBUG)
# Log messages once
_logger.propagate = False
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(levelname)s: %(message)s")
formatter.formatException = lambda exc_info: _formatException(*exc_info)
handler.setFormatter(formatter)
_logger.addHandler(handler)
class _Unbuffered:
"""
Disable buffering for standard output and standard error.
https://stackoverflow.com/a/107717
https://docs.python.org/3/library/io.html
"""
def __init__(self, stream):
self.stream = stream
def __getattr__(self, attr):
return getattr(self.stream, attr)
def write(self, b):
self.stream.write(b)
self.stream.flush()
def writelines(self, lines):
self.stream.writelines(lines)
self.stream.flush()
sys.stderr = _Unbuffered(sys.stderr)
sys.stdout = _Unbuffered(sys.stdout)
def _formatException(type, value, tb):
"""
Format traceback, darkening entries from global site-packages directories
and user-specific site-packages directory.
https://stackoverflow.com/a/46071447/5156190
"""
# Absolute paths to site-packages
packages = tuple(join(abspath(p), "") for p in sys.path[1:])
# Highlight lines not referring to files in site-packages
lines = []
for line in format_exception(type, value, tb):
matches = re.search(r"^ File \"([^\"]+)\", line \d+, in .+", line)
if matches and matches.group(1).startswith(packages):
lines += line
else:
matches = re.search(r"^(\s*)(.*?)(\s*)$", line, re.DOTALL)
lines.append(
matches.group(1)
+ colored(matches.group(2), "yellow")
+ matches.group(3)
)
return "".join(lines).rstrip()
sys.excepthook = lambda type, value, tb: print(
_formatException(type, value, tb), file=sys.stderr
)
def eprint(*args, **kwargs):
raise RuntimeError(
"The CS50 Library for Python no longer supports eprint, but you can use print instead!"
)
def get_char(prompt):
raise RuntimeError(
"The CS50 Library for Python no longer supports get_char, but you can use get_string instead!"
)
def get_float(prompt):
"""
Read a line of text from standard input and return the equivalent float
as precisely as possible; if text does not represent a double, user is
prompted to retry. If line can't be read, return None.
"""
while True:
s = get_string(prompt)
if s is None:
return None
if len(s) > 0 and re.search(r"^[+-]?\d*(?:\.\d*)?$", s):
try:
return float(s)
except (OverflowError, ValueError):
pass
def get_int(prompt):
"""
Read a line of text from standard input and return the equivalent int;
if text does not represent an int, user is prompted to retry. If line
can't be read, return None.
"""
while True:
s = get_string(prompt)
if s is None:
return None
if re.search(r"^[+-]?\d+$", s):
try:
return int(s, 10)
except ValueError:
pass
def get_string(prompt):
"""
Read a line of text from standard input and return it as a string,
sans trailing line ending. Supports CR (\r), LF (\n), and CRLF (\r\n)
as line endings. If user inputs only a line ending, returns "", not None.
Returns None upon error or no input whatsoever (i.e., just EOF).
"""
if not isinstance(prompt, str):
raise TypeError("prompt must be of type str")
try:
return input(prompt)
except EOFError:
return None