234 lines
7.4 KiB
Python
234 lines
7.4 KiB
Python
#!/usr/bin/env python
|
|
"""
|
|
ptpython: Interactive Python shell.
|
|
|
|
positional arguments:
|
|
args Script and arguments
|
|
|
|
optional arguments:
|
|
-h, --help show this help message and exit
|
|
--vi Enable Vi key bindings
|
|
-i, --interactive Start interactive shell after executing this file.
|
|
--asyncio Run an asyncio event loop to support top-level "await".
|
|
--light-bg Run on a light background (use dark colors for text).
|
|
--dark-bg Run on a dark background (use light colors for text).
|
|
--config-file CONFIG_FILE
|
|
Location of configuration file.
|
|
--history-file HISTORY_FILE
|
|
Location of history file.
|
|
-V, --version show program's version number and exit
|
|
|
|
environment variables:
|
|
PTPYTHON_CONFIG_HOME: a configuration directory to use
|
|
PYTHONSTARTUP: file executed on interactive startup (no default)
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import os
|
|
import pathlib
|
|
import sys
|
|
from textwrap import dedent
|
|
from typing import IO
|
|
|
|
import appdirs
|
|
from prompt_toolkit.formatted_text import HTML
|
|
from prompt_toolkit.shortcuts import print_formatted_text
|
|
|
|
from ptpython.repl import PythonRepl, embed, enable_deprecation_warnings, run_config
|
|
|
|
try:
|
|
from importlib import metadata # type: ignore
|
|
except ImportError:
|
|
import importlib_metadata as metadata # type: ignore
|
|
|
|
|
|
__all__ = ["create_parser", "get_config_and_history_file", "run"]
|
|
|
|
|
|
class _Parser(argparse.ArgumentParser):
|
|
def print_help(self, file: IO[str] | None = None) -> None:
|
|
super().print_help()
|
|
print(
|
|
dedent(
|
|
"""
|
|
environment variables:
|
|
PTPYTHON_CONFIG_HOME: a configuration directory to use
|
|
PYTHONSTARTUP: file executed on interactive startup (no default)
|
|
""",
|
|
).rstrip(),
|
|
)
|
|
|
|
|
|
def create_parser() -> _Parser:
|
|
parser = _Parser(description="ptpython: Interactive Python shell.")
|
|
parser.add_argument("--vi", action="store_true", help="Enable Vi key bindings")
|
|
parser.add_argument(
|
|
"-i",
|
|
"--interactive",
|
|
action="store_true",
|
|
help="Start interactive shell after executing this file.",
|
|
)
|
|
parser.add_argument(
|
|
"--asyncio",
|
|
action="store_true",
|
|
help='Run an asyncio event loop to support top-level "await".',
|
|
)
|
|
parser.add_argument(
|
|
"--light-bg",
|
|
action="store_true",
|
|
help="Run on a light background (use dark colors for text).",
|
|
)
|
|
parser.add_argument(
|
|
"--dark-bg",
|
|
action="store_true",
|
|
help="Run on a dark background (use light colors for text).",
|
|
)
|
|
parser.add_argument(
|
|
"--config-file", type=str, help="Location of configuration file."
|
|
)
|
|
parser.add_argument("--history-file", type=str, help="Location of history file.")
|
|
parser.add_argument(
|
|
"-V",
|
|
"--version",
|
|
action="version",
|
|
version=metadata.version("ptpython"),
|
|
)
|
|
parser.add_argument("args", nargs="*", help="Script and arguments")
|
|
return parser
|
|
|
|
|
|
def get_config_and_history_file(namespace: argparse.Namespace) -> tuple[str, str]:
|
|
"""
|
|
Check which config/history files to use, ensure that the directories for
|
|
these files exist, and return the config and history path.
|
|
"""
|
|
config_dir = os.environ.get(
|
|
"PTPYTHON_CONFIG_HOME",
|
|
appdirs.user_config_dir("ptpython", "prompt_toolkit"),
|
|
)
|
|
data_dir = appdirs.user_data_dir("ptpython", "prompt_toolkit")
|
|
|
|
# Create directories.
|
|
for d in (config_dir, data_dir):
|
|
pathlib.Path(d).mkdir(parents=True, exist_ok=True)
|
|
|
|
# Determine config file to be used.
|
|
config_file = os.path.join(config_dir, "config.py")
|
|
legacy_config_file = os.path.join(os.path.expanduser("~/.ptpython"), "config.py")
|
|
|
|
warnings = []
|
|
|
|
# Config file
|
|
if namespace.config_file:
|
|
# Override config_file.
|
|
config_file = os.path.expanduser(namespace.config_file)
|
|
|
|
elif os.path.isfile(legacy_config_file):
|
|
# Warn about the legacy configuration file.
|
|
warnings.append(
|
|
HTML(
|
|
" <i>~/.ptpython/config.py</i> is deprecated, move your configuration to <i>%s</i>\n"
|
|
)
|
|
% config_file
|
|
)
|
|
config_file = legacy_config_file
|
|
|
|
# Determine history file to be used.
|
|
history_file = os.path.join(data_dir, "history")
|
|
legacy_history_file = os.path.join(os.path.expanduser("~/.ptpython"), "history")
|
|
|
|
if namespace.history_file:
|
|
# Override history_file.
|
|
history_file = os.path.expanduser(namespace.history_file)
|
|
|
|
elif os.path.isfile(legacy_history_file):
|
|
# Warn about the legacy history file.
|
|
warnings.append(
|
|
HTML(
|
|
" <i>~/.ptpython/history</i> is deprecated, move your history to <i>%s</i>\n"
|
|
)
|
|
% history_file
|
|
)
|
|
history_file = legacy_history_file
|
|
|
|
# Print warnings.
|
|
if warnings:
|
|
print_formatted_text(HTML("<u>Warning:</u>"))
|
|
for w in warnings:
|
|
print_formatted_text(w)
|
|
|
|
return config_file, history_file
|
|
|
|
|
|
def run() -> None:
|
|
a = create_parser().parse_args()
|
|
|
|
config_file, history_file = get_config_and_history_file(a)
|
|
|
|
# Startup path
|
|
startup_paths = []
|
|
if "PYTHONSTARTUP" in os.environ:
|
|
startup_paths.append(os.environ["PYTHONSTARTUP"])
|
|
|
|
# --interactive
|
|
if a.interactive and a.args:
|
|
# Note that we shouldn't run PYTHONSTARTUP when -i is given.
|
|
startup_paths = [a.args[0]]
|
|
sys.argv = a.args
|
|
|
|
# Add the current directory to `sys.path`.
|
|
if sys.path[0] != "":
|
|
sys.path.insert(0, "")
|
|
|
|
# When a file has been given, run that, otherwise start the shell.
|
|
if a.args and not a.interactive:
|
|
sys.argv = a.args
|
|
path = a.args[0]
|
|
with open(path, "rb") as f:
|
|
code = compile(f.read(), path, "exec")
|
|
# NOTE: We have to pass a dict as namespace. Omitting this argument
|
|
# causes imports to not be found. See issue #326.
|
|
# However, an empty dict sets __name__ to 'builtins', which
|
|
# breaks `if __name__ == '__main__'` checks. See issue #444.
|
|
exec(code, {"__name__": "__main__", "__file__": path})
|
|
|
|
# Run interactive shell.
|
|
else:
|
|
enable_deprecation_warnings()
|
|
|
|
# Apply config file
|
|
def configure(repl: PythonRepl) -> None:
|
|
if os.path.exists(config_file):
|
|
run_config(repl, config_file)
|
|
|
|
# Adjust colors if dark/light background flag has been given.
|
|
if a.light_bg:
|
|
repl.min_brightness = 0.0
|
|
repl.max_brightness = 0.60
|
|
elif a.dark_bg:
|
|
repl.min_brightness = 0.60
|
|
repl.max_brightness = 1.0
|
|
|
|
import __main__
|
|
|
|
embed_result = embed( # type: ignore
|
|
vi_mode=a.vi,
|
|
history_filename=history_file,
|
|
configure=configure,
|
|
locals=__main__.__dict__,
|
|
globals=__main__.__dict__,
|
|
startup_paths=startup_paths,
|
|
title="Python REPL (ptpython)",
|
|
return_asyncio_coroutine=a.asyncio,
|
|
)
|
|
|
|
if a.asyncio:
|
|
print("Starting ptpython asyncio REPL")
|
|
print('Use "await" directly instead of "asyncio.run()".')
|
|
asyncio.run(embed_result)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run()
|