diff --git a/packages/foreignthon/src/foreignthon/cli.py b/packages/foreignthon/src/foreignthon/cli.py index 6c8e953..b2b5e82 100644 --- a/packages/foreignthon/src/foreignthon/cli.py +++ b/packages/foreignthon/src/foreignthon/cli.py @@ -1,15 +1,13 @@ from __future__ import annotations -import runpy import sys -import tempfile from pathlib import Path import click from . import __version__ from .errors import activate -from .transpiler import transpile_file +from .transpiler import transpile_file, run_transpiled @click.group() @@ -26,15 +24,13 @@ def main(): def run(file: Path, lang: str | None, keep: bool): """Transpile and run a foreign-language Python file.""" if lang: - # Inject shebang override so transpiler picks it up source = file.read_text(encoding="utf-8") if not source.startswith("# foreignthon:"): source = f"# foreignthon: {lang}\n" + source - file.write_text(source, encoding="utf-8") + file.write_text(source, encoding="utf-8") transpiled = transpile_file(file) - # Detect lang for error hook detected_lang = lang or _lang_from_file(file) activate(detected_lang) @@ -43,17 +39,7 @@ def run(file: Path, lang: str | None, keep: bool): out_path.write_text(transpiled, encoding="utf-8") click.echo(f"Compiled: {out_path}") - # Write to a temp file and run it — runpy keeps __file__ correct - with tempfile.NamedTemporaryFile( - suffix=".py", mode="w", encoding="utf-8", delete=False - ) as tmp: - tmp.write(transpiled) - tmp_path = tmp.name - - try: - runpy.run_path(tmp_path, run_name="__main__") - finally: - Path(tmp_path).unlink(missing_ok=True) + run_transpiled(file, transpiled) @main.command() diff --git a/packages/foreignthon/src/foreignthon/transpiler.py b/packages/foreignthon/src/foreignthon/transpiler.py index 9409448..8aeeabd 100644 --- a/packages/foreignthon/src/foreignthon/transpiler.py +++ b/packages/foreignthon/src/foreignthon/transpiler.py @@ -52,7 +52,7 @@ def transpile(source: str, lang_code: str) -> str: def transpile_file(path: Path) -> str: """ - Detect language from file extension (.es.py → es), + Detect language from file extension (.es.py -> es), read the file, and return transpiled Python source. """ lang_code = _detect_lang(path) @@ -64,8 +64,33 @@ def transpile_file(path: Path) -> str: return transpile(source, lang_code) +def run_transpiled(original_path: Path, transpiled: str) -> None: + """ + Execute transpiled source while making tracebacks point + to the original .es.py file, not a temp file. + """ + import linecache + + filename = str(original_path.resolve()) + + # Register original source lines so traceback displays them correctly + original_lines = original_path.read_text(encoding="utf-8").splitlines(keepends=True) + linecache.cache[filename] = ( + len(original_lines), + None, + original_lines, + filename, + ) + + # Compile with original filename — this is what sets it in the traceback + code = compile(transpiled, filename, "exec") + + glob = {"__file__": filename, "__name__": "__main__"} + exec(code, glob) + + def _detect_lang(path: Path) -> str: - """Extract lang code from extension, e.g. script.es.py → es.""" + """Extract lang code from extension, e.g. script.es.py -> es.""" suffixes = path.suffixes # e.g. ['.es', '.py'] if len(suffixes) >= 2 and suffixes[-1] == ".py": return suffixes[-2].lstrip(".")