Files
Templated-PPT-Master/scripts/prepare_native_project.py
T

97 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""Add a bundled native builder to a clean initialized project, without relocking."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import shutil
import sys
import tempfile
import ppt_project as project_cli
import template_ref as ref
def prepare(root: Path):
root = project_cli.root_path(root)
config, _ = project_cli.check(root)
ref.require(config["authoring"]["route"] == "native", "prepare requires authoring.route=native")
ref.require(config["build"]["entrypoint"] is None and not config["build"]["inputs"], "prepare refuses an existing/custom builder")
environment = project_cli.safe_path(root, config["environment"], "environment")
old_environment = environment.read_bytes()
env = ref.read_json(environment, (1,))
ref.require(not env["packages"] and not env["fonts"] and not env["tools"], "prepare refuses to overwrite a configured environment")
manifest = ref.load_manifest(root / config["template"]["manifest"])
ref.require(manifest["schema_version"] == 2, "prepare requires manifest schema 2")
template_root = (root / config["template"]["manifest"]).parent
style = ref.read_json(ref.file_path(template_root, manifest["style"], "style"), None)
workflow = root / "vendor/native-workflow"
builder = root / "tools/native_builder.py"
ref.require(not workflow.exists() and not builder.exists(), "prepare refuses existing native-workflow or native_builder.py paths")
skill = Path(__file__).resolve().parent.parent
provenance = ref.read_json(skill / "runtime/provenance.json", (1,))
package_lines = (skill / "runtime/requirements.txt").read_text(encoding="utf-8").splitlines()
packages = dict(line.split("==", 1) for line in package_lines if line.strip() and not line.startswith("#"))
fonts = [{"role": role, **entry, "distributed": False, "embedded": False,
"verification": "declared by the selected template; installation, file identity and licenses require destination readback"}
for role, entry in style["typography"]["families"].items()]
new_env = {"schema_version": 1, "python": {"minimum": "3.12", "project_cli_minimum": "3.10"},
"packages": packages, "fonts": fonts,
"tools": [{"name": provenance["component"], "version": provenance["version"],
"path": "vendor/native-workflow/runtime/ppt-master", "source": provenance["source"],
"license": provenance["license"], "upstream_modified": False}],
"external_executables": [], "capabilities": {"image_generation": "host tool when needed",
"PowerPoint": "host application and supported UI tool for application checks"}}
old_config = (root / "project.json").read_bytes()
created = []
with tempfile.TemporaryDirectory(prefix="native-prepare-", dir=root.parent) as temporary:
staging = Path(temporary) / "native-workflow"
staging.mkdir()
shutil.copytree(skill / "runtime", staging / "runtime", ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo"))
(staging / "scripts").mkdir()
for name in ("native_export.py", "finalize_theme.py", "template_ref.py", "math_component.py", "slide_number.py",
"component_typography.py", "paragraph_layout.py", "text_block.py", "layout_spacing.py"):
shutil.copyfile(skill / "scripts" / name, staging / "scripts" / name)
# The snapshot is an explicit build input; no installed Skill is needed
# after this project is moved to a different directory or machine.
inputs = ["vendor/native-workflow/" + path.relative_to(staging).as_posix()
for path in sorted(staging.rglob("*")) if path.is_file()]
try:
shutil.copytree(staging, workflow)
created.append(workflow)
with builder.open("xb") as target:
target.write((skill / "scripts/native_builder.py").read_bytes())
created.append(builder)
config["build"]["entrypoint"] = "tools/native_builder.py"
config["build"]["inputs"] = inputs
project_cli.atomic_json(environment, new_env)
project_cli.atomic_json(root / "project.json", config)
project_cli.check(root, author_lock=False)
except Exception:
environment.write_bytes(old_environment)
(root / "project.json").write_bytes(old_config)
for item in reversed(created):
shutil.rmtree(item) if item.is_dir() else item.unlink(missing_ok=True)
raise
return {"ok": True, "project": str(root), "builder": config["build"]["entrypoint"],
"copied_inputs": len(inputs), "author_lock_updated": False,
"next": "Review source and environment changes, add finalized page SVGs/notes, then run tools/ppt_project.py lock --project ."}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--project", type=Path, required=True)
args = parser.parse_args()
try:
print(json.dumps(prepare(args.project), ensure_ascii=False, indent=2))
return 0
except (ref.InputError, OSError, ValueError, KeyError, TypeError) as error:
print(json.dumps({"error": str(error)}, ensure_ascii=False), file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())