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 @click.group() @click.version_option(__version__, prog_name="fpy") def main(): """ForeignThon — write Python in any language.""" pass @main.command() @click.argument("file", type=click.Path(exists=True, path_type=Path)) @click.option("--lang", "-l", default=None, help="Override language code (e.g. es, ta)") @click.option("--keep", is_flag=True, help="Keep the compiled .py file alongside the source") 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") transpiled = transpile_file(file) # Detect lang for error hook detected_lang = lang or _lang_from_file(file) activate(detected_lang) if keep: out_path = file.with_suffix("").with_suffix(".compiled.py") 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) @main.command() @click.argument("file", type=click.Path(exists=True, path_type=Path)) @click.option("--output", "-o", default=None, help="Output path (default: same dir as source)") def compile(file: Path, output: str | None): """Transpile a file to standard Python without running it.""" transpiled = transpile_file(file) out_path = Path(output) if output else file.with_suffix("").with_suffix(".compiled.py") out_path.write_text(transpiled, encoding="utf-8") click.echo(f"Compiled: {out_path}") @main.command() @click.argument("file", type=click.Path(exists=True, path_type=Path)) def check(file: Path): """Validate a foreign-language file without running it.""" import ast try: transpiled = transpile_file(file) ast.parse(transpiled) click.echo(f"✓ {file.name} looks good.") except SyntaxError as e: click.echo(f"✗ Syntax error: {e}", err=True) sys.exit(1) except Exception as e: click.echo(f"✗ {e}", err=True) sys.exit(1) @main.command("pack") @click.argument("json_file", type=click.Path(exists=True, path_type=Path)) def validate_pack(json_file: Path): """Validate a language pack JSON file.""" import json from .pack import REQUIRED_SECTIONS with open(json_file, encoding="utf-8") as f: data = json.load(f) missing = REQUIRED_SECTIONS - data.keys() if missing: click.echo(f"✗ Missing sections: {missing}", err=True) sys.exit(1) click.echo(f"✓ Pack '{data['meta']['name']}' is valid.") def _lang_from_file(path: Path) -> str: suffixes = path.suffixes if len(suffixes) >= 2 and suffixes[-1] == ".py": return suffixes[-2].lstrip(".") return "en"