"""µFrame CLI — render, compile, check, and deploy .uf files. Usage: python -m uframe.cli render [--ascii | --micron] [--width N] python -m uframe.cli compile [--out ] [--embed] python -m uframe.cli check python -m uframe.cli deploy [--dest ] """ from __future__ import annotations import argparse import os import sys from pathlib import Path import uframe from uframe.errors import UFrameError def cmd_render(args: argparse.Namespace) -> int: """Render a .uf file to ASCII and/or Micron.""" source = Path(args.file).read_text(encoding="utf-8") try: result = uframe.compile(source, width=args.width, theme=getattr(args, 'theme', '')) except UFrameError as e: print(f"Error: {e}", file=sys.stderr) return 1 if args.ascii: print(result.ascii) elif args.micron: print(result.micron) else: # Default: show ASCII print(result.ascii) if result.warnings: for w in result.warnings: print(f"Warning: {w.message}", file=sys.stderr) if result.is_dynamic: print(f"\n[Dynamic page — {len(result.script)} bytes of generated script]", file=sys.stderr) return 0 def cmd_compile(args: argparse.Namespace) -> int: """Compile a .uf file to an executable .mu script (dynamic) or static .mu.""" source_path = Path(args.file) source = source_path.read_text(encoding="utf-8") try: result = uframe.compile(source, width=args.width, theme=getattr(args, 'theme', '')) except UFrameError as e: print(f"Error: {e}", file=sys.stderr) return 1 out_path = Path(args.out) if args.out else source_path.with_suffix(".mu") if result.is_dynamic and result.script: out_path.write_text(result.script, encoding="utf-8") out_path.chmod(0o755) print(f"Compiled dynamic: {out_path} ({len(result.script)} bytes, +x)") else: out_path.write_text(result.micron, encoding="utf-8") print(f"Compiled static: {out_path} ({len(result.micron)} bytes)") return 0 def cmd_check(args: argparse.Namespace) -> int: """Validate a .uf file without generating output.""" source = Path(args.file).read_text(encoding="utf-8") try: result = uframe.compile(source, width=args.width, theme=getattr(args, 'theme', '')) except UFrameError as e: print(f"Error: {e}", file=sys.stderr) return 1 status = "dynamic" if result.is_dynamic else "static" print(f"OK: {args.file} ({status}, {len(result.ascii)} chars ASCII, {len(result.micron)} chars Micron)") if result.warnings: for w in result.warnings: print(f" Warning: {w.message}") return 0 def cmd_deploy(args: argparse.Namespace) -> int: """Compile and deploy a .uf file to the NomadNet pages directory.""" source_path = Path(args.file) source = source_path.read_text(encoding="utf-8") dest_dir = Path(args.dest or os.path.expanduser("~/.nomadnetwork/storage/pages")) try: result = uframe.compile(source, width=args.width, theme=getattr(args, 'theme', '')) except UFrameError as e: print(f"Error: {e}", file=sys.stderr) return 1 dest_dir.mkdir(parents=True, exist_ok=True) out_path = dest_dir / f"{source_path.stem}.mu" if result.is_dynamic and result.script: out_path.write_text(result.script, encoding="utf-8") out_path.chmod(0o755) print(f"Deployed dynamic: {out_path}") else: out_path.write_text(result.micron, encoding="utf-8") out_path.chmod(0o644) print(f"Deployed static: {out_path}") return 0 def cmd_image(args: argparse.Namespace) -> int: """Convert an image file to character art.""" from uframe.imaging import convert_image try: lines = convert_image(args.file, mode=args.mode, width=args.width, dither=args.dither, invert=args.invert) for line in lines: print(line) except ImportError: print("Error: Pillow is required: pip install Pillow", file=sys.stderr) return 1 except FileNotFoundError: print(f"Error: Image not found: {args.file}", file=sys.stderr) return 1 return 0 def main() -> int: parser = argparse.ArgumentParser( prog="uframe", description="µFrame — A DSL for rich terminal UIs rendered as ASCII and Micron", ) sub = parser.add_subparsers(dest="command", required=True) # render p_render = sub.add_parser("render", help="Render a .uf file") p_render.add_argument("file", help="Path to .uf source file") p_render.add_argument("--ascii", action="store_true", help="Output ASCII only") p_render.add_argument("--micron", action="store_true", help="Output Micron only") p_render.add_argument("--width", type=int, default=64, help="Page width (default: 64)") p_render.add_argument("--theme", default="", help="Theme name (default, nouveau, gothic, bamboo, circuit, brutalist)") # compile p_compile = sub.add_parser("compile", help="Compile to .mu file") p_compile.add_argument("file", help="Path to .uf source file") p_compile.add_argument("--out", help="Output file path (default: .mu)") p_compile.add_argument("--width", type=int, default=64, help="Page width") p_compile.add_argument("--theme", default="", help="Theme name") # check p_check = sub.add_parser("check", help="Validate a .uf file") p_check.add_argument("file", help="Path to .uf source file") p_check.add_argument("--width", type=int, default=64, help="Page width") # deploy p_deploy = sub.add_parser("deploy", help="Compile and deploy to NomadNet") p_deploy.add_argument("file", help="Path to .uf source file") p_deploy.add_argument("--dest", help="Destination directory (default: ~/.nomadnetwork/storage/pages)") p_deploy.add_argument("--width", type=int, default=64, help="Page width") p_deploy.add_argument("--theme", default="", help="Theme name") # image (standalone conversion) p_image = sub.add_parser("image", help="Convert image to character art") p_image.add_argument("file", help="Path to image file") p_image.add_argument("--mode", default="braille", help="braille, block, ascii, halfblock") p_image.add_argument("--width", type=int, default=40, help="Output width") p_image.add_argument("--dither", default="floyd", help="floyd, threshold, none") p_image.add_argument("--invert", action="store_true", help="Invert light/dark") args = parser.parse_args() commands = { "render": cmd_render, "compile": cmd_compile, "check": cmd_check, "deploy": cmd_deploy, "image": cmd_image, } return commands[args.command](args) if __name__ == "__main__": sys.exit(main())