Last active
April 17, 2026 16:14
-
-
Save qexat/3b6dbb57edc68973f3432f6eb79c5be7 to your computer and use it in GitHub Desktop.
my .pythonrc (2026.04.17)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # pyright: reportUnusedCallResult = false, reportUnusedImport = false | |
| # ruff: noqa: DTZ005, T201 | |
| """ | |
| .pythonrc is a file which is executed when the Python interactive shell is | |
| started if $PYTHONSTARTUP is in your environment and points to this file. | |
| It's just regular Python code, so do what you will. Your ~/.inputrc file | |
| can greatly complement this file. | |
| Modified from sontek's dotfiles repo on github: | |
| https://github.com/sontek/dotfiles | |
| """ | |
| from __future__ import annotations | |
| import abc | |
| import ast | |
| import atexit | |
| import builtins | |
| import calendar as _calendar | |
| import collections.abc | |
| import contextlib | |
| import copy | |
| import ctypes # noqa: F401 | |
| import dataclasses | |
| import datetime | |
| import decimal | |
| import faulthandler | |
| import functools | |
| import importlib.util | |
| import inspect | |
| import io | |
| import math # noqa: F401 | |
| import operator # noqa: F401 | |
| import os | |
| import platform | |
| import random | |
| import re | |
| import shutil | |
| import subprocess | |
| import sys | |
| import textwrap | |
| import traceback | |
| import typing | |
| if typing.TYPE_CHECKING: | |
| import types | |
| _PRIDE_MONTH_NUMBER = 6 | |
| __MIN_RECURSION_LIMIT = 0x800 | |
| __MAX_RECURSION_LIMIT = 0x8000 | |
| __MIN_INT_MAX_STR_DIGITS = 100_000 | |
| __MAX_INT_MAX_STR_DIGITS = 2_000_000 | |
| ############# | |
| # LAUNCHING # | |
| ############# | |
| LAUNCHING_MESSAGE = ( | |
| "\x1b[35m◉ \x1b[1mLaunching the custom REPL...\x1b[22;39m" | |
| ) | |
| VIRTUAL_ENV = os.environ.get("VIRTUAL_ENV", None) | |
| HOME = ( | |
| VIRTUAL_ENV | |
| or os.environ.get("WORKON_HOME", None) | |
| or os.environ["HOME"] | |
| ) | |
| ############# | |
| # TYPE VARS # | |
| ############# | |
| AnyCallable: typing.TypeAlias = collections.abc.Callable[..., typing.Any] | |
| AnyCallableT = typing.TypeVar("AnyCallableT", bound=AnyCallable) | |
| _T = typing.TypeVar("_T") | |
| _T0 = typing.TypeVar("_T0") | |
| _T1 = typing.TypeVar("_T1") | |
| _T2 = typing.TypeVar("_T2") | |
| _P = typing.ParamSpec("_P") | |
| _R = typing.TypeVar("_R") | |
| ############# | |
| # CONSTANTS # | |
| ############# | |
| DECIMAL_CONTEXT = decimal.getcontext() | |
| _FIVE_SQRT = decimal.Decimal(5.0**0.5) | |
| phi: typing.Final[float] = (1.0 + 5.0**0.5) / 2.0 | |
| psi: typing.Final = 1.0 - phi | |
| phi_decimal: typing.Final = decimal.Decimal(phi) | |
| psi_decimal: typing.Final = decimal.Decimal(psi) | |
| python: typing.Final = builtins | |
| ################################# | |
| # SAVE & RESTORE HISTORY STATES # | |
| ################################# | |
| try: | |
| import readline | |
| except ImportError: | |
| pass | |
| else: | |
| ################## | |
| # TAB COMPLETION # | |
| ################## | |
| try: | |
| import rlcompleter # noqa: F401 | |
| except ImportError: | |
| pass | |
| else: | |
| if sys.platform == "darwin": | |
| # Work around a bug in Mac OS X's readline module. | |
| readline.parse_and_bind("bind ^I rl_complete") | |
| else: | |
| readline.parse_and_bind("tab: complete") | |
| ###################### | |
| # PERSISTENT HISTORY # | |
| ###################### | |
| # Use separate history files for each virtual environment. | |
| HISTORY_FILE = os.path.join(HOME, ".pyhistory") | |
| # Read the existing history if there is one. | |
| if os.path.exists(HISTORY_FILE): | |
| try: | |
| readline.read_history_file(HISTORY_FILE) | |
| except Exception: # noqa: BLE001 | |
| # If there was a problem reading the history file then it may | |
| # have become corrupted, so we just delete it. | |
| os.remove(HISTORY_FILE) | |
| # Set maximum number of commands written to the history file. | |
| readline.set_history_length(256) | |
| @atexit.register | |
| def savehist() -> None: | |
| """ | |
| Save the history of the shell into a `.pyhistory` file. | |
| Automatically runs when the user exists the shell. | |
| """ | |
| try: | |
| readline.write_history_file(HISTORY_FILE) | |
| except NameError: | |
| pass | |
| except Exception as err: # noqa: BLE001 | |
| message = ( | |
| f"Unable to save history file due to the following " | |
| f"error: {err}" | |
| ) | |
| print(message, file=sys.stderr) | |
| ################# | |
| # COLOR SUPPORT # | |
| ################# | |
| class TermColors(dict[str, str]): | |
| """ | |
| Gives easy access to ANSI color codes. | |
| Attempts to fall back to no color for certain TERM values. | |
| Mostly taken from IPython. | |
| """ | |
| COLOR_TEMPLATES = ( | |
| ("Black", "30"), | |
| ("Red", "31"), | |
| ("Green", "32"), | |
| ("Brown", "33"), | |
| ("Blue", "34"), | |
| ("Purple", "35"), | |
| ("Cyan", "36"), | |
| ("LightGray", "37"), | |
| ("DarkGray", "90"), | |
| ("LightRed", "91"), | |
| ("LightGreen", "92"), | |
| ("Yellow", "93"), | |
| ("LightBlue", "94"), | |
| ("LightPurple", "95"), | |
| ("LightCyan", "96"), | |
| ("White", "97"), | |
| ("Normal", "39"), | |
| ) | |
| NoColor = "" | |
| _base = "\033[%sm" | |
| def __init__(self) -> None: | |
| if os.environ.get("TERM") in { | |
| "xterm-color", | |
| "xterm-256color", | |
| "linux", | |
| "screen", | |
| "screen-256color", | |
| "screen-bce", | |
| }: | |
| self.update( | |
| (k, self._base % v) for k, v in self.COLOR_TEMPLATES | |
| ) | |
| else: | |
| self.update((k, self.NoColor) for k, _ in self.COLOR_TEMPLATES) | |
| _c: typing.Final = TermColors() | |
| ############################## | |
| # BETTER SYNTAX HIGHLIGHTING # | |
| ############################## | |
| with contextlib.suppress(ImportError, ModuleNotFoundError): | |
| import _colorize | |
| _theme = _colorize.get_theme() | |
| _colorize.set_theme( | |
| _theme.copy_with( | |
| syntax=_colorize.Syntax( | |
| comment=_colorize.ANSIColors.INTENSE_BLACK, | |
| keyword=_colorize.ANSIColors.BOLD_MAGENTA, | |
| keyword_constant=_colorize.ANSIColors.MAGENTA, | |
| soft_keyword=_colorize.ANSIColors.INTENSE_MAGENTA, | |
| builtin=_colorize.ANSIColors.BLUE, | |
| number=_colorize.ANSIColors.CYAN, | |
| op=_colorize.ANSIColors.WHITE, | |
| definition=_colorize.ANSIColors.BLUE, | |
| ), | |
| ), | |
| ) | |
| ################# | |
| # FAKE COMMANDS # | |
| ################# | |
| class FakeCommand(abc.ABC): | |
| """Class to inherit to create your own "fake" command.""" | |
| def __repr__(self) -> str: | |
| self() | |
| return "\u200b" # zero-width space | |
| @abc.abstractmethod | |
| def __call__(self) -> typing.Any: # noqa: D102 | |
| pass | |
| class _ExitConsole(FakeCommand): | |
| """Exit command (no parentheses needed!).""" | |
| def __call__(self, code: int = 0) -> typing.Never: | |
| raise SystemExit(code) | |
| class _CalendarCommand(FakeCommand): | |
| """Command to display the calendar.""" | |
| def __call__(self) -> None: | |
| print(_calendar.calendar(datetime.date.today().year, m=4)) # noqa: DTZ011 | |
| @dataclasses.dataclass(repr=False, frozen=True) | |
| class ShellCommand(FakeCommand): | |
| """Run shell commands directly in your Python REPL. Do NOT abuse.""" | |
| cmd: str | |
| def __call__(self) -> None: # noqa: D102 | |
| STATE.status = subprocess.call([self.cmd]) # noqa: S603 | |
| ######### | |
| # UTILS # | |
| ######### | |
| class _UtilsList(FakeCommand, list[AnyCallable]): | |
| """Utilitary to list and provide info on REPL utils.""" | |
| __name__ = "utils" | |
| def __call__(self, function: AnyCallable | None = None) -> None: | |
| buffer = io.StringIO() | |
| if function is None: | |
| for element in self: | |
| buffer.write(f"\x1b[1;37m{element.__name__}\x1b[22;39m") | |
| if element.__doc__: | |
| doc = textwrap.dedent(element.__doc__) | |
| first_line = doc.strip().splitlines()[0] | |
| if not first_line.endswith("."): | |
| first_line += "..." | |
| function_comment = ( | |
| f" \x1b[2m·\x1b[22m \x1b[3;92m" | |
| f"{first_line}\x1b[23;39m" | |
| ) | |
| buffer.write(f"{function_comment:>48}") | |
| buffer.write("\n") | |
| else: | |
| buffer.write( | |
| f"def {function.__name__}" | |
| f"{inspect.signature(function, eval_str=True)}" | |
| ) | |
| if function.__doc__: | |
| doc = textwrap.indent( | |
| f"\"\"\"\n{function.__doc__.strip('\n')}\n\"\"\"", | |
| " " * 4, | |
| ) | |
| buffer.write(f"\n\x1b[92m{doc}\x1b[39m") | |
| builtins.print(buffer.getvalue()) | |
| __annotations__ = __call__.__annotations__ | |
| def register( | |
| self, | |
| function: AnyCallableT, | |
| *, | |
| with_name: str | None = None, | |
| ) -> AnyCallableT: | |
| _function = copy.copy(function) | |
| self.append(_function) | |
| if with_name: | |
| _function.__name__ = with_name | |
| self.sort(key=lambda function: function.__name__) | |
| return _function | |
| utils: typing.Final = _UtilsList() | |
| utils.append(utils) | |
| literal: typing.Final = utils.register( | |
| ast.literal_eval, | |
| with_name="literal", | |
| ) | |
| @utils.register | |
| def concat(*strings: str, sep: str = " ") -> str: | |
| """ | |
| Concatenate strings together giving a separator `sep` (default: " "). | |
| """ | |
| return sep.join(strings) | |
| @utils.register | |
| def clamp(number: int, _min: int = 0, _max: int = sys.maxsize) -> int: | |
| """ | |
| Minmax an integer to a `min` (default: 0) and a `max` | |
| (default: sys.maxsize). | |
| """ | |
| return max(_min, min(_max, number)) | |
| @utils.register | |
| def clear() -> None: | |
| """ | |
| Clear the terminal. | |
| """ | |
| is_pride_month = datetime.datetime.now().month == _PRIDE_MONTH_NUMBER | |
| sys.stdout.write("\x1b[H\x1b[J") | |
| sys.stdout.write( | |
| make_pill( | |
| get_pretty_python_version(), | |
| get_pretty_date(), | |
| *( | |
| ( | |
| "\x1b[1m" | |
| "\x1b[38;5;9mH" | |
| "\x1b[38;5;1ma" | |
| "\x1b[38;5;3mp" | |
| "\x1b[38;5;11mp" | |
| "\x1b[38;5;10my " | |
| "\x1b[38;5;2mP" | |
| "\x1b[38;5;14mr" | |
| "\x1b[38;5;6mi" | |
| "\x1b[38;5;12md" | |
| "\x1b[38;5;4me " | |
| "\x1b[38;5;5mM" | |
| "\x1b[38;5;13mo" | |
| "\x1b[38;5;15mn" | |
| "\x1b[38;5;7mt" | |
| "\x1b[38;5;8mh" | |
| "\x1b[38;5;0m!" | |
| "\x1b[22;39m", | |
| ) | |
| if is_pride_month | |
| else () | |
| ), | |
| get_pretty_time(), | |
| ), | |
| ) | |
| sys.stdout.write("\n") | |
| @utils.register | |
| def choose(*values: object) -> object: | |
| """ | |
| Pick a random element among the values. | |
| Variadic equivalent of `random.choice()`. | |
| """ | |
| return random.choice(values) | |
| @utils.register | |
| def compose( | |
| func1: collections.abc.Callable[[_T0], _T1], | |
| func2: collections.abc.Callable[[_T1], _T2], | |
| ) -> collections.abc.Callable[[_T0], _T2]: | |
| """ | |
| Compose two functions together from left to right. | |
| >>> compose(str, len)(36) | |
| 2 | |
| """ | |
| return lambda arg: func2(func1(arg)) | |
| @utils.register | |
| def flipped( | |
| func: collections.abc.Callable[[_T0, _T1], _T2], | |
| ) -> collections.abc.Callable[[_T1, _T0], _T2]: | |
| """ | |
| Return a version of the function with its arguments flipped. | |
| >>> flipped(operator.sub)(3, 5) | |
| 2 | |
| """ | |
| return lambda arg1, arg2: func(arg2, arg1) | |
| @utils.register | |
| def raw_length(value: str) -> int: | |
| """ | |
| Calculate the raw length of the string, i.e. its "displayed" length. | |
| """ | |
| return compose(esclean, len)(value) | |
| @utils.register | |
| def float_equal(f1: float, f2: float) -> bool: | |
| """ | |
| Estimate if two floats are equal using the machine's epsilon. | |
| >>> float_equal(0.1 + 0.2, 0.3) | |
| True | |
| """ | |
| return 0 <= abs(f2 - f1) <= sys.float_info.epsilon | |
| @utils.register | |
| def irange(start: int, stop: int) -> range: | |
| """ | |
| `range`, but with inclusive stop. | |
| >>> irange(0, 10) | |
| range(0, 11) | |
| """ | |
| return range(start, stop + 1) | |
| @utils.register | |
| def magic(string: str) -> list[str]: | |
| """ | |
| Convert a string into a list of the hexadecimal values of its | |
| characters. | |
| >>> magic("hello") | |
| ['0x68', '0x65', '0x6c', '0x6c', '0x6f'] | |
| """ | |
| return list(map(hex, string.encode("utf-8"))) | |
| @utils.register | |
| def unpack[T, U]( | |
| fn: collections.abc.Callable[[collections.abc.Iterable[T]], U], | |
| ) -> collections.abc.Callable[[*tuple[T, ...]], U]: | |
| """ | |
| Unpack the parameters of the provided function. | |
| >>> unpack(list)(3, 5, 2) | |
| [3, 5, 2] | |
| """ | |
| def f(*args: *tuple[T, ...]) -> U: | |
| return fn(args) | |
| return f | |
| s = unpack(" ".join) | |
| _map_curried = lambda fn: lambda xs: map(fn, xs) | |
| fmap = lambda fn: unpack(_map_curried(fn)) | |
| @utils.register | |
| def zeros_iter( | |
| n: int = -1, / | |
| ) -> collections.abc.Generator[int, None, None]: | |
| """ | |
| Return an iterator of `n` zeros. | |
| """ | |
| i = 0 | |
| while i < n or n < 0: | |
| yield 0 | |
| i += 1 | |
| @utils.register | |
| def zeros(n: int, /) -> list[int]: | |
| """ | |
| Return a list of `n` zeros. | |
| Raises | |
| ------ | |
| ValueError | |
| If `n` is less than 0. | |
| Examples | |
| -------- | |
| >>> zeros(5) | |
| [0, 0, 0, 0, 0] | |
| >>> zeros(0) | |
| [] | |
| >>> zeros(-2) | |
| *- ValueError: n must be non-negative -* | |
| """ | |
| if n < 0: | |
| message = "n must be non-negative" | |
| raise ValueError(message) | |
| return list(zeros_iter(n)) | |
| @utils.register | |
| def bool_to_sign(value: bool, /) -> typing.Literal[1, -1]: # noqa: FBT001 | |
| """ | |
| Return 1 if value is `True`, else -1. | |
| >>> bool_to_sign(True) | |
| 1 | |
| >>> bool_to_sign(False) | |
| -1 | |
| """ | |
| return 1 if value else -1 | |
| @utils.register | |
| def parity_to_sign( | |
| value: int, | |
| /, | |
| *, | |
| inverted: bool = False, | |
| ) -> typing.Literal[1, -1]: | |
| """ | |
| Return 1 if value is even, else -1. For consistency, 0 is considered | |
| even. | |
| If `inverted` is set to `True`, the result is negated. | |
| >>> parity_to_sign(2) | |
| 1 | |
| >>> parity_to_sign(-3) | |
| -1 | |
| >>> parity_to_sign(0) | |
| 1 | |
| >>> parity_to_sign(40, inverted=True) | |
| -1 | |
| """ | |
| return bool_to_sign(value % 2 == inverted) | |
| @utils.register | |
| def fib(n: int) -> int: | |
| """The Fibonacci sequence over the integers.""" | |
| if n < 0: | |
| return parity_to_sign(n, inverted=True) * fib(abs(n)) | |
| if n <= 1: | |
| return n | |
| return fib(n - 2) + fib(n - 1) | |
| @utils.register | |
| def fib_fast(n: int) -> int: | |
| """ | |
| A fast implementation of the Fibonacci sequence. | |
| """ | |
| _n = decimal.Decimal(n) | |
| return int( | |
| ( | |
| DECIMAL_CONTEXT.power(phi_decimal, _n) | |
| - DECIMAL_CONTEXT.power(psi_decimal, _n) | |
| ) | |
| / _FIVE_SQRT, | |
| ) | |
| @utils.register | |
| def os_colorify(string: str) -> str: | |
| """ | |
| Stylize the `string` with the color of the OS. | |
| Raises | |
| ------ | |
| OSError | |
| If the operating system is not Linux or the distribution does not | |
| have a color. | |
| """ | |
| if sys.platform != "linux": | |
| message = "this function can only run on Linux" | |
| raise OSError(message) | |
| info = platform.freedesktop_os_release() | |
| if "ANSI_COLOR" not in info: | |
| message = "operating system does not have a color" | |
| raise OSError(message) | |
| return f"\x1b[{info['ANSI_COLOR']}m{string}\x1b[39m" | |
| @typing.final | |
| class Canvas(list[list[bool]]): | |
| @staticmethod | |
| def new(width: int, height: int) -> Canvas: | |
| return Canvas( | |
| ([False for _ in range(width + 1)]) for _ in range(height + 1) | |
| ) | |
| def set_point(self, *, x: int, y: int) -> None: | |
| if y >= len(self): | |
| self.extend([] for _ in range(y - len(self) + 1)) | |
| xs = self[y] | |
| if x >= len(xs): | |
| xs.extend(False for _ in range(x - len(xs) + 1)) | |
| xs[x] = True | |
| def _uniformize_lengths(self) -> None: | |
| max_length = max(map(len, self)) | |
| for xs in self: | |
| if len(xs) < max_length: | |
| xs.extend(False for _ in range(max_length - len(xs))) | |
| def render(self, *, symbol: str = "*") -> str: | |
| lines = ["".join(symbol if x else " " for x in xs) for xs in self] | |
| return "\n".join(reversed(lines)) | |
| def print(self, *, symbol: str = "*") -> None: | |
| print(self.render(symbol=symbol)) | |
| class Plot: | |
| def __init__( | |
| self, | |
| *, | |
| x: range | None = None, | |
| y: range | None = None, | |
| ) -> None: | |
| columns, lines = shutil.get_terminal_size() | |
| self.canvas: typing.Final = Canvas() | |
| self.x: typing.Final = range(0, columns, 1) if x is None else x | |
| self.y: typing.Final = range(0, lines - 5, 1) if y is None else y | |
| def __contains__(self, position: tuple[int, int]) -> bool: | |
| (x, y) = position | |
| return x in self.x and y in self.y | |
| def add_function( | |
| self, | |
| function: typing.Callable[[int], int], | |
| ) -> typing.Self: | |
| for xi in self.x: | |
| yi = function(xi) | |
| if (xi, yi) in self: | |
| self.canvas.set_point(x=xi, y=yi) | |
| return self | |
| def print(self, *, symbol: str = "*") -> None: | |
| self.canvas.print(symbol=symbol) | |
| @utils.register | |
| def display_function( | |
| function: typing.Callable[[int], int], | |
| *, | |
| symbol: str = "*", | |
| x: range | None = None, | |
| y: range | None = None, | |
| ) -> None: | |
| plot = Plot(x=x, y=y) | |
| plot.add_function(function) | |
| plot.print(symbol=symbol) | |
| ################################ | |
| # PRETTY PRINT OUTPUT & ERRORS # | |
| ################################ | |
| def get_pretty_python_version() -> str: | |
| """Render a colorful piece of text of the current Python version.""" | |
| string = "\x1b[1mPython\x1b[0m \x1b[91m{}.{}.{}\x1b[39m" | |
| return string.format(*sys.version_info[:3]) | |
| def get_pretty_date() -> str: | |
| """Render a colorful piece of text of the current date.""" | |
| return datetime.datetime.now().strftime( | |
| "\x1b[1;35m%A %d %B %Y\x1b[22;39m" | |
| ) | |
| def get_pretty_time() -> str: | |
| """Render a colorful piece of text of the current time.""" | |
| return datetime.datetime.now().strftime("\x1b[1;34m%H:%M\x1b[22;39m") | |
| @utils.register | |
| def esclean(string: str) -> str: | |
| """ | |
| Clean the string from escape sequences. | |
| Regex shamelessly stolen from a tutorialspoint post: | |
| https://www.tutorialspoint.com/How-can-I-remove-the-ANSI-escape-sequences-from-a-string-in-python | |
| """ | |
| return re.compile(r"(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]").sub("", string) | |
| @utils.register | |
| def make_pill(main: str, *others: str) -> str: | |
| """ | |
| Render a contiguous array of blocks containing text. | |
| TODO: fix the case where the pill ends up being bigger than the | |
| available terminal width. | |
| >>> make_pill("hello", "world") | |
| ╭───────┬───────╮ | |
| │ hello │ world │ | |
| ╰───────┴───────╯ | |
| """ | |
| buffer = io.StringIO() | |
| main_len = raw_length(main) | |
| others_len = [raw_length(other) for other in others] | |
| buffer.write("╭" + "─" * (main_len + 2)) | |
| for length in others_len: | |
| buffer.write("┬" + "─" * (length + 2)) | |
| buffer.write("╮\n") | |
| buffer.write("│ " + " │ ".join((main, *others)) + " │\n") | |
| buffer.write("╰" + "─" * (main_len + 2)) | |
| for length in others_len: | |
| buffer.write("┴" + "─" * (length + 2)) | |
| buffer.write("╯") | |
| return buffer.getvalue() | |
| exit = utils.register(_ExitConsole(), with_name="exit") # noqa: A001 | |
| fastfetch = ShellCommand("fastfetch") | |
| calendar = _CalendarCommand() | |
| class ReplState: | |
| """ | |
| The state of the REPL (mainly, its status code). | |
| """ | |
| def __init__(self) -> None: | |
| self.status = 0 | |
| def mutator( | |
| self, *, status: int | None = None | |
| ) -> collections.abc.Callable[ | |
| [collections.abc.Callable[_P, _R]], | |
| collections.abc.Callable[_P, _R], | |
| ]: | |
| """ | |
| Set a function `func` to be a REPL state mutator. | |
| When the function `func` is called, set the state provided as | |
| keyword arguments to this decorator. | |
| Returns | |
| ------- | |
| The decorator that takes the function as argument. | |
| """ | |
| def decorator( | |
| func: collections.abc.Callable[_P, _R], / | |
| ) -> collections.abc.Callable[_P, _R]: | |
| """ | |
| Decorator returned by `mutator`. | |
| Returns | |
| ------- | |
| The transformed function that mutates the state. | |
| """ | |
| @functools.wraps(func) | |
| def inner(*args: _P.args, **kwargs: _P.kwargs) -> _R: | |
| result = func(*args, **kwargs) | |
| if status is not None: | |
| self.status = status | |
| return result | |
| return inner | |
| return decorator | |
| STATE = ReplState() | |
| class DynamicPrompt(abc.ABC): | |
| """ | |
| Prompt that dynamically adapts according to the state of the REPL. | |
| """ | |
| def __init__(self, state: ReplState) -> None: | |
| self._state = state | |
| def __repr__(self) -> str: | |
| return self.prompt() | |
| @abc.abstractmethod | |
| def prompt(self) -> str: | |
| """Return the dynamic prompt.""" | |
| class DynamicPS1(DynamicPrompt): | |
| """ | |
| Main prompt that dynamically adapts according to the state of the REPL. | |
| """ | |
| def prompt(self) -> str: # noqa: D102 | |
| color = _c["LightRed"] if self._state.status else _c["LightGreen"] | |
| return " %sλ%s " % (color, _c["Normal"]) # noqa: UP031 | |
| class DynamicPS2(DynamicPrompt): | |
| """ | |
| Secondary prompt string that dynamically adapts according to what is | |
| happening in the REPL. | |
| """ | |
| def prompt(self) -> str: # noqa: D102 | |
| color = _c["LightRed"] if self._state.status else _c["LightGreen"] | |
| return f"\x1b[m{' ' * (raw_length(repr(sys.ps1)) - 2)}%s┊%s " % ( | |
| color, | |
| _c["Normal"], | |
| ) | |
| # Make the prompts colorful. | |
| sys.ps1 = DynamicPS1(STATE) | |
| sys.ps2 = DynamicPS2(STATE) | |
| # Enable pretty printing for STDOUT | |
| @STATE.mutator(status=0) | |
| def pretty_display_hook(value: object) -> None: | |
| """Custom display hook to make printed values colorful and pretty.""" | |
| if value is not None: | |
| __builtins__._ = value | |
| builtins.print(repr(value)) | |
| sys.displayhook = pretty_display_hook | |
| # Make errors and tracebacks stand out a bit more. | |
| def pretty_except_hook( | |
| exc_type: type[BaseException], | |
| exc_value: BaseException, | |
| exc_tb: types.TracebackType | None, | |
| ) -> None: | |
| """ | |
| Custom exception hook to make them look colorful and pretty. | |
| """ | |
| sys.stderr.write(_c["Yellow"]) | |
| traceback.print_exception(exc_type, exc_value, exc_tb) | |
| sys.stderr.write(_c["Normal"]) | |
| # NOTE: There is a bug (?) in Python 3, where a trailing color marker | |
| # that's written to STDERR or STDOUT by itself does not color the | |
| # subsequent lines. | |
| # We work around this by manually calling ``flush`` afterwards. | |
| sys.stderr.flush() | |
| # Python 3.13 introduces pretty exceptions | |
| if sys.version_info < (3, 13): | |
| sys.excepthook = pretty_except_hook | |
| sys.excepthook = STATE.mutator(status=1)(sys.excepthook) | |
| _setrecursionlimit: typing.Final = sys.setrecursionlimit | |
| _set_int_max_str_digits: typing.Final = sys.set_int_max_str_digits | |
| def recursion_limit_setter_safe(limit: int, /) -> None: | |
| """\ | |
| Set the recursion limit of the Python interpreter. | |
| This function overrides the built-in to prevent basic footguns. | |
| Raises | |
| ------ | |
| ValueError | |
| When `limit` is lower than 2048 or higher than 32768. | |
| """ | |
| if limit > __MAX_RECURSION_LIMIT: | |
| message = ( | |
| "cannot set recursion limit to higher than 32768 " | |
| "\x1b[35m[prevent-large-recursion]\x1b[39m", | |
| ) | |
| raise ValueError(message) | |
| if limit < __MIN_RECURSION_LIMIT: | |
| message = ( | |
| "cannot set recursion limit to lower than 2048 " | |
| "\x1b[35m[prevent-small-recursion]\x1b[39m", | |
| ) | |
| raise ValueError(message) | |
| _setrecursionlimit(limit) | |
| def int_max_str_digits_setter_safe(limit: int, /) -> None: | |
| """ | |
| Set the max count of digits for integers to be converted to a string. | |
| This function overrides the built-in to prevent basic footguns. | |
| Raises | |
| ------ | |
| ValueError | |
| When `limit` is lower than 10'000 or higher than 200'000. | |
| """ | |
| if limit > __MAX_INT_MAX_STR_DIGITS: | |
| message = "cannot set int max str digit limit to higher than 200'000" | |
| raise ValueError(message) | |
| if limit < __MIN_INT_MAX_STR_DIGITS: | |
| message = "cannot set int max str digit limit to lower than 10'000" | |
| raise ValueError(message) | |
| _set_int_max_str_digits(limit) | |
| # Prevents a footgun ^^ | |
| sys.setrecursionlimit = recursion_limit_setter_safe | |
| sys.set_int_max_str_digits = _set_int_max_str_digits | |
| # Funny things happen here vvv | |
| @utils.register | |
| def hex_encode(string: str) -> str: | |
| """ | |
| Encode the string as the hexadecimal representation of its bytes. | |
| >>> hex_encode("hello") | |
| 68656C6C6F | |
| """ | |
| return f"{int.from_bytes(string.encode()):X}" | |
| @utils.register | |
| def hex_decode(encoded: str, length: int) -> str: | |
| """Decode the hexadecimal representation of a string's bytes into the \ | |
| original object. | |
| >>> hex_decode("68656C6C6F", 5) | |
| hello | |
| """ | |
| return int(encoded, 16).to_bytes(length).replace(b"\x00", b"").decode() | |
| @utils.register | |
| def encode_funcname( | |
| function: collections.abc.Callable[..., typing.Any], | |
| ) -> str: | |
| """ | |
| Encode a function as an unique identifier that can be used to | |
| retrieve the function object later. | |
| >>> encode_funcname(abs) | |
| _6275696C74696E73_616273 | |
| """ | |
| return ( | |
| "_" | |
| + hex_encode(function.__module__) | |
| + "_" | |
| + hex_encode(function.__name__) | |
| ) | |
| @utils.register | |
| def decode_funcname( | |
| byteid: str, | |
| ) -> collections.abc.Callable[..., typing.Any]: | |
| """ | |
| Decode the identifier generated by `encode_funcname` and retreive, if | |
| it exists, the original function object. | |
| Raises | |
| ------ | |
| ValueError | |
| When `byteid` is invalid. | |
| Examples | |
| -------- | |
| >>> decode_funcname("_6275696C74696E73_616273") | |
| <built-in function abs> | |
| """ | |
| _, module_id, func_id = byteid.split("_") | |
| module_name = hex_decode(module_id, 64) | |
| func_name = hex_decode(func_id, 64) | |
| try: | |
| return getattr(importlib.import_module(module_name), func_name) | |
| except Exception: # noqa: BLE001 | |
| message = "invalid func byte id" | |
| raise ValueError(message) from None | |
| @utils.register | |
| def identity(value: _T) -> _T: | |
| """ | |
| Implementation of the I combinator (λa.a). | |
| """ | |
| return value | |
| @utils.register | |
| def kestrel(first: _T, _: object) -> _T: | |
| """ | |
| Implementation of the K combinator (λab.a). | |
| """ | |
| return first | |
| @utils.register | |
| def kite(_: object, second: _T) -> _T: | |
| """ | |
| Implementation of the KI combinator (λab.b). | |
| """ | |
| return second | |
| @utils.register | |
| def starling( | |
| func1: collections.abc.Callable[[_T0, _T1], _T2], | |
| func2: collections.abc.Callable[[_T0], _T1], | |
| value: _T0, | |
| /, | |
| ) -> _T2: | |
| """ | |
| Implementation of the S combinator (λa.λb.λc.ac(bc)). | |
| """ | |
| return func1(value, func2(value)) | |
| @utils.register | |
| def thrush(value: _T0, func: collections.abc.Callable[[_T0], _T1]) -> _T1: | |
| """ | |
| Implementation of the T combinator (λa.λb.ba). | |
| """ | |
| return func(value) | |
| @utils.register | |
| def inductive( | |
| operation: collections.abc.Callable[[_T0, _T1], _T1], | |
| fixpoint_predicate: collections.abc.Callable[[_T0], bool], | |
| fixpoint_value: _T1, | |
| decreasing: collections.abc.Callable[[_T0], _T0], | |
| ) -> collections.abc.Callable[[_T0], _T1]: | |
| """ | |
| Produce an inductive function given an operation, a fixpoint and a | |
| decreasing function. | |
| Parameters | |
| ---------- | |
| operation : (T, U) -> U | |
| A binary function that takes the current argument and the value of | |
| the previous iteration and returns a new value. | |
| For example, for the factorial, it would be the multiplication. | |
| fixpoint_predicate : (T) -> bool | |
| A predicate that determines whether the current iteration's | |
| argument has reached the base case where the induction should stop. | |
| fixpoint_value : U | |
| The value that is returned when the base case is reached, on top of | |
| all the results of the previous iterations. | |
| decreasing : (T) -> T | |
| A unary function that minimally decreases the current iteration | |
| argument's value. The result is used as the argument of the next | |
| iteration, so make sure it can actually reached the fixpoint | |
| predicate. | |
| For example, in the case where `T` is `int`, it should be | |
| `n -> n - 1`. | |
| Examples | |
| -------- | |
| >>> fact = inductive( | |
| lambda a, b: a * b, # a = n, b = fact(n - 1) | |
| lambda a: a <= 1, # base case | |
| 1, # for n <= 1, fact(n) = 1 | |
| lambda a: a - 1, | |
| ) | |
| >>> fact(10) | |
| 3628800 | |
| """ | |
| def function(value: _T0) -> _T1: | |
| def function_auxiliary(current: _T0, previous: _T1) -> _T1: | |
| if fixpoint_predicate(current): | |
| return previous | |
| return function_auxiliary( | |
| decreasing(current), | |
| operation(current, previous), | |
| ) | |
| return function_auxiliary(value, fixpoint_value) | |
| return function | |
| src: typing.Final = utils.register(inspect.getsource, with_name="src") | |
| def help(request: object = None, /) -> None: # noqa: A001, D103 | |
| builtins.help(request) | |
| sys.stdout.write("\x1b[?1049h") | |
| clear() | |
| def __disable_if_not_tty( | |
| func: collections.abc.Callable[[], None], | |
| ) -> collections.abc.Callable[[], None]: | |
| if sys.stdout.isatty(): | |
| return func | |
| return lambda: None | |
| @atexit.register | |
| @__disable_if_not_tty | |
| def _exit_alt_buffer() -> None: # pyright: ignore[reportUnusedFunction] | |
| """\ | |
| Disable the alternative screen buffer at exit. | |
| """ | |
| import sys # for IPython | |
| sys.stdout.write("\x1b[?1049l") | |
| sys.stdout.flush() | |
| print("\x1b[32m♡ \x1b[1mThank you for using qexat's .pythonrc\x1b[22;39m") | |
| __has_initialized = False | |
| def init(_: list[str]) -> None: | |
| """ | |
| The default init function of the REPL. | |
| Tasks | |
| ----- | |
| * Print a launching message | |
| * Enable the fault handler | |
| * Set the recursion limit to a higher, but safe limit | |
| * Switch to the alternative screen buffer | |
| * Clear the screen | |
| If the REPL is already initialized, do nothing. | |
| """ | |
| if __has_initialized: | |
| return | |
| print(LAUNCHING_MESSAGE) | |
| # Sometimes I do some fuckery in the REPL so we should get ourselves | |
| # covered | |
| faulthandler.enable() | |
| # 1000 is too low... | |
| sys.setrecursionlimit(__MAX_RECURSION_LIMIT) | |
| DECIMAL_CONTEXT.prec = 10_000 | |
| sys.set_int_max_str_digits(100_000) | |
| # Enter the alternative screen buffer | |
| # We do NOT use `print` because `rich` strips the escape sequence | |
| # somehow | |
| sys.stdout.write("\x1b[?1049h") | |
| sys.stdout.flush() | |
| # We clear the buffer so it looks like a new window | |
| clear() | |
| def main( | |
| init_func: collections.abc.Callable[[list[str]], None], | |
| *, | |
| args: list[str], | |
| ) -> None: | |
| """ | |
| Entry point of the REPL. | |
| Parameters | |
| ---------- | |
| init_func : (list[str]) -> None | |
| A function run at the initialization of the REPL. | |
| args : list[str] | |
| The CLI arguments passed to the REPL. | |
| """ | |
| global __has_initialized # noqa: PLW0603 | |
| if not sys.stdout.isatty(): | |
| print("\x1b[1;31mError:\x1b[22;39m piping output is not supported\n") | |
| raise SystemExit(1) | |
| if __has_initialized: | |
| builtins.print( | |
| "\x1b[1;93mWARNING: " | |
| "\x1b[39mREPL is already initialized\x1b[22m", | |
| file=sys.stderr, | |
| ) | |
| init_func(args) | |
| __has_initialized = True | |
| if __name__ == "__main__": | |
| main(init, args=sys.argv[1:]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment