-
Notifications
You must be signed in to change notification settings - Fork 17
feat: add transparent Python CLI wrapper #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| import sys | ||
| import os | ||
| import re | ||
| import subprocess | ||
| import yaml | ||
|
|
||
| SCRIPTDIR = os.path.dirname(__file__) | ||
|
|
@@ -56,6 +57,17 @@ def build_alias_map(): | |
| DEFAULT_LANGUAGE_MAP = build_language_map() | ||
| DEFAULT_ALIAS_MAP = build_alias_map() | ||
|
|
||
| UP_FLAGS = { | ||
| '-t', '--translate', | ||
| '-d', '--dictionary', | ||
| '-sl', '--source-language', | ||
| '-r', '--reverse', | ||
| '-re', '--return', | ||
| '-k', '--keep', | ||
| '-ko', '--keep-only', | ||
| '-o', '--options', | ||
| } | ||
|
|
||
| def detect_language_from_filename(filename): | ||
| """Detect language from file extension (e.g., my-program.de.py -> german) | ||
| Returns tuple: (filepath, two_letter_code) or None""" | ||
|
|
@@ -147,7 +159,93 @@ def run_module( | |
| mod = importlib.import_module(".modes."+mode, package='universalpython') | ||
| return mod.run(args, code) | ||
|
|
||
| def passthrough_to_python(): | ||
| """Replace this process with the real Python interpreter, forwarding all args.""" | ||
| if os.name == 'nt': | ||
| sys.exit(subprocess.call([sys.executable] + sys.argv[1:])) | ||
| os.execvp(sys.executable, [sys.executable] + sys.argv[1:]) | ||
|
|
||
|
|
||
| def print_version(): | ||
| """Print UniversalPython version alongside the Python version.""" | ||
| try: | ||
| from importlib.metadata import version as get_version | ||
| up_version = get_version("universalpython") | ||
| except Exception: | ||
| up_version = "dev" | ||
| py_version = sys.version.split()[0] | ||
| print(f"UniversalPython {up_version} (Python {py_version})") | ||
|
|
||
|
|
||
| def print_help(): | ||
| """Print UP-specific help, noting that unknown flags pass through to Python.""" | ||
| print( | ||
| "UniversalPython \u2014 Python, but in your native language.\n" | ||
| "\n" | ||
| "Usage:\n" | ||
| " universalpython <file> Compile & run a UniversalPython file\n" | ||
| " universalpython --options <file> Compile & run a UP file (explicit)\n" | ||
| " universalpython --version | -V Show UP + Python version\n" | ||
| " universalpython --help | -h Show this help message\n" | ||
| "\n" | ||
| "UP compile options:\n" | ||
| " -o, --options <file> Compile & run a UniversalPython file\n" | ||
| " -t, --translate [engine] Translate identifiers (unidecode | argostranslate)\n" | ||
| " -d, --dictionary <path> Path to language dictionary YAML\n" | ||
| " -sl, --source-language <code> Source language code (e.g. fr, de)\n" | ||
| " -r, --reverse Reverse-translate (English \u2192 target language)\n" | ||
| " -re, --return Return compiled code instead of executing\n" | ||
| " -k, --keep Save compiled .en.py file and run\n" | ||
| " -ko, --keep-only Save compiled .en.py file without running\n" | ||
| "\n" | ||
| "Anything else (e.g. -c, -m, script.py) is forwarded to Python as-is." | ||
| ) | ||
|
|
||
|
|
||
| def main(): | ||
| cli_args = sys.argv[1:] | ||
|
|
||
| if not cli_args: | ||
| passthrough_to_python() | ||
| return | ||
|
|
||
| first = cli_args[0] | ||
|
|
||
| if first in ('--version', '-V'): | ||
| print_version() | ||
| return | ||
|
|
||
| if first in ('--help', '-h'): | ||
| print_help() | ||
| return | ||
|
|
||
| if first in ('--options', '-o'): | ||
| if len(cli_args) < 2: | ||
| print("Error: --options requires a filename.") | ||
| return | ||
| filename = cli_args[1] | ||
| try: | ||
| with open(filename, encoding='utf-8') as f: | ||
| code = f.read() | ||
| except FileNotFoundError: | ||
| print(f"Error: file not found: {filename}") | ||
| return | ||
| args = { | ||
| 'file': [filename], | ||
| 'translate': False, | ||
| 'dictionary': "", | ||
| 'source_language': "", | ||
| 'reverse': False, | ||
| 'keep': False, | ||
| 'keep_only': False, | ||
| 'return': False, | ||
| } | ||
| return run_module('lex', code, args) | ||
|
|
||
| if first.startswith('-') and first not in UP_FLAGS: | ||
| passthrough_to_python() | ||
| return | ||
|
|
||
| import argparse | ||
|
|
||
| # construct the argument parser and parse the argument | ||
|
|
@@ -180,7 +278,7 @@ def main(): | |
| help="Translate English code to the language of your choice.") | ||
|
|
||
| ap.add_argument("-re", "--return", | ||
| action='store_false', | ||
| action='store_true', | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmm... why was this needed?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My bad, this must stay store_false, i must have been experimenting with something |
||
| default=False, required=False, | ||
| help="Return the code instead of executing (used in module mode).") | ||
|
|
||
|
|
@@ -197,6 +295,9 @@ def main(): | |
|
|
||
| args = vars(ap.parse_args()) | ||
|
|
||
| if args['dictionary']: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why was this needed this time? It seems like it might affect compilation ahead.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was getting this error: D:\UniversalPython>universalpython test\samples\de\hello.de.py --dictionary languages\de\default.yaml Before the fix, languages\de\default.yaml was passed as-is to lex.py (a relative path). After the fix, os.path.abspath('languages\de\default.yaml') converts it to D:\UniversalPython\universalpython\languages\de\default.yaml (the actual location of the file) before it reaches lex.py. So lex.py gets a full path |
||
| args['dictionary'] = os.path.abspath(args['dictionary']) | ||
|
|
||
| filename = args["file"][0] | ||
| with open(filename, encoding='utf-8') as code_pyfile: | ||
| code = code_pyfile.read() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you run
python --helpand append its output here (filtering out the ones which conflict with UniversalPython's flags, instead of writing 'anything else'?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
D:\UniversalPython>universalpython --help
UniversalPython — Python, but in your native language.
Usage:
universalpython Compile & run a UniversalPython file
universalpython --options Compile & run a UP file (explicit)
universalpython --version | -V Show UP + Python version
universalpython --help | -h Show this help message
UP compile options:
-o, --options Compile & run a UniversalPython file
-t, --translate [engine] Translate identifiers (unidecode | argostranslate)
-d, --dictionary Path to language dictionary YAML
-sl, --source-language
Source language code (e.g. fr, de)-r, --reverse Reverse-translate (English → target language)
-re, --return Return compiled code instead of executing
-k, --keep Save compiled .en.py file and run
-ko, --keep-only Save compiled .en.py file without running
The following Python flags are passed through directly:
Options (and corresponding environment variables):
-b : issue warnings about str(bytes_instance), str(bytearray_instance)
and comparing bytes/bytearray with str. (-bb: issue errors)
-B : don't write .pyc files on import; also PYTHONDONTWRITEBYTECODE=x
-c cmd : program passed in as string (terminates option list)
-E : ignore PYTHON* environment variables (such as PYTHONPATH)
-i : inspect interactively after running script; forces a prompt even
if stdin does not appear to be a terminal; also PYTHONINSPECT=x
-I : isolate Python from the user's environment (implies -E and -s)
-m mod : run library module as a script (terminates option list)
-O : remove assert and debug-dependent statements; add .opt-1 before
.pyc extension; also PYTHONOPTIMIZE=x
-OO : do -O changes and also discard docstrings; add .opt-2 before
.pyc extension
-P : don't prepend a potentially unsafe path to sys.path
-q : don't print version and copyright messages on interactive startup
-s : don't add user site directory to sys.path; also PYTHONNOUSERSITE
-S : don't imply 'import site' on initialization
-u : force the stdout and stderr streams to be unbuffered;
this option has no effect on stdin; also PYTHONUNBUFFERED=x
-v : verbose (trace import statements); also PYTHONVERBOSE=x
can be supplied multiple times to increase verbosity
-W arg : warning control; arg is action:message:category:module:lineno
also PYTHONWARNINGS=arg
-x : skip first line of source, allowing use of non-Unix forms of #!cmd
-X opt : set implementation-specific option
--check-hash-based-pycs always|default|never:
control how Python invalidates hash-based .pyc files
--help-env : print help about Python environment variables and exit
--help-xoptions : print help about implementation-specific -X options and exit
--help-all : print complete help information and exit
Arguments:
file : program read from script file
arg ...: arguments passed to program in sys.argv[1:]
Is this the output you were expecting?