160 lines
4.3 KiB
Python
160 lines
4.3 KiB
Python
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
|