diff --git a/src/foreignthon/cli.py b/src/foreignthon/cli.py index e69de29..6c8e953 100644 --- a/src/foreignthon/cli.py +++ b/src/foreignthon/cli.py @@ -0,0 +1,111 @@ +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"