feat: component
This commit is contained in:
165
backend/uframe/cli.py
Normal file
165
backend/uframe/cli.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""µFrame CLI — render, compile, check, and deploy .uf files.
|
||||
|
||||
Usage:
|
||||
python -m uframe.cli render <file.uf> [--ascii | --micron] [--width N]
|
||||
python -m uframe.cli compile <file.uf> [--out <file.mu>] [--embed]
|
||||
python -m uframe.cli check <file.uf>
|
||||
python -m uframe.cli deploy <file.uf> [--dest <dir>]
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import stat
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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 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)")
|
||||
|
||||
# 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: <name>.mu)")
|
||||
p_compile.add_argument("--width", type=int, default=64, help="Page width")
|
||||
|
||||
# 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")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
commands = {
|
||||
"render": cmd_render,
|
||||
"compile": cmd_compile,
|
||||
"check": cmd_check,
|
||||
"deploy": cmd_deploy,
|
||||
}
|
||||
|
||||
return commands[args.command](args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user