251 lines
6.4 KiB
Python
Executable File
251 lines
6.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import xml.etree.ElementTree as ET
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
INKSCAPE_NS = "http://www.inkscape.org/namespace/inkscape/extension"
|
|
ET.register_namespace("", INKSCAPE_NS)
|
|
|
|
REPOS = [
|
|
{
|
|
"name": "botbox3000",
|
|
"url": "https://github.com/jondale/botbox3000.git",
|
|
"branch": "main",
|
|
"directory": "botbox3000",
|
|
"inx": [
|
|
{
|
|
"file": "boxbot.inx",
|
|
"id": "org.knoxmakers.botbox",
|
|
"submenu": ["Knox Makers", "Laser"],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"name": "km-living-hinge",
|
|
"url": "https://github.com/KnoxMakers/km-living-hinge.git",
|
|
"branch": "main",
|
|
"directory": "km-living-hinge",
|
|
},
|
|
{
|
|
"name": "km-plot",
|
|
"url": "https://git.knoxmakers.org/KnoxMakers/km-plot.git",
|
|
"branch": "main",
|
|
"directory": "km-plot",
|
|
},
|
|
{
|
|
"name": "km-hatch",
|
|
"url": "https://git.knoxmakers.org/KnoxMakers/km-hatch.git",
|
|
"branch": "main",
|
|
"directory": "km-hatch",
|
|
},
|
|
{
|
|
"name": "km-hershey",
|
|
"url": "https://git.knoxmakers.org/KnoxMakers/km-hershey.git",
|
|
"branch": "main",
|
|
"directory": "km-hershey",
|
|
},
|
|
]
|
|
|
|
|
|
def run(cmd: list[str], cwd: str | None = None) -> str:
|
|
result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, check=True)
|
|
return result.stdout.strip()
|
|
|
|
|
|
def has_staged_changes(root: Path) -> bool:
|
|
result = subprocess.run(
|
|
["git", "diff", "--cached", "--quiet"],
|
|
cwd=root,
|
|
capture_output=True,
|
|
)
|
|
return result.returncode != 0
|
|
|
|
|
|
def sync_repo(repo: dict, ext_dir: Path, tmp_dir: Path) -> None:
|
|
name = repo["name"]
|
|
url = repo["url"]
|
|
branch = repo.get("branch", "main")
|
|
directory = repo["directory"]
|
|
|
|
work = tmp_dir / name
|
|
target = ext_dir / directory
|
|
|
|
print(f"==> Sync {name} from {url} ({branch}) -> extensions/{directory}")
|
|
|
|
subprocess.run(
|
|
["git", "clone", "--depth", "1", "--branch", branch, url, str(work)],
|
|
check=True,
|
|
)
|
|
|
|
if target.exists():
|
|
shutil.rmtree(target)
|
|
shutil.copytree(work, target)
|
|
|
|
git_dir = target / ".git"
|
|
if git_dir.exists():
|
|
shutil.rmtree(git_dir)
|
|
|
|
commit = run(["git", "rev-parse", "HEAD"], cwd=str(work))
|
|
(target / ".upstream_commit").write_text(commit + "\n")
|
|
(target / ".upstream_url").write_text(url + "\n")
|
|
(target / ".upstream_branch").write_text(branch + "\n")
|
|
|
|
|
|
def set_extension_id(root: ET.Element, new_id: str) -> bool:
|
|
ns = {"ink": INKSCAPE_NS}
|
|
|
|
id_elem = root.find("ink:id", ns)
|
|
if id_elem is None:
|
|
id_elem = root.find("id")
|
|
|
|
if id_elem is not None:
|
|
if id_elem.text != new_id:
|
|
id_elem.text = new_id
|
|
return True
|
|
return False
|
|
|
|
name_elem = root.find("ink:name", ns)
|
|
if name_elem is None:
|
|
name_elem = root.find("name")
|
|
|
|
id_elem = ET.Element("id")
|
|
id_elem.text = new_id
|
|
|
|
if name_elem is not None:
|
|
idx = list(root).index(name_elem)
|
|
root.insert(idx + 1, id_elem)
|
|
else:
|
|
root.insert(0, id_elem)
|
|
return True
|
|
|
|
|
|
def set_effects_submenu(root: ET.Element, submenus: list[str]) -> bool:
|
|
ns = {"ink": INKSCAPE_NS}
|
|
|
|
effect_elem = root.find(".//ink:effect", ns)
|
|
if effect_elem is None:
|
|
effect_elem = root.find(".//effect")
|
|
|
|
if effect_elem is None:
|
|
return False
|
|
|
|
menu_elem = None
|
|
for tag in ["effects-menu", "effectsmenu"]:
|
|
menu_elem = effect_elem.find(tag)
|
|
if menu_elem is not None:
|
|
break
|
|
menu_elem = effect_elem.find(f"ink:{tag}", ns)
|
|
if menu_elem is not None:
|
|
break
|
|
|
|
if menu_elem is None:
|
|
return False
|
|
|
|
menu_elem.clear()
|
|
menu_elem.tag = "effects-menu"
|
|
|
|
parent = menu_elem
|
|
for i, submenu_name in enumerate(submenus):
|
|
submenu = ET.SubElement(parent, "submenu")
|
|
submenu.set("_name", submenu_name)
|
|
if i < len(submenus) - 1:
|
|
parent = submenu
|
|
|
|
return True
|
|
|
|
|
|
def patch_inx(inx_path: Path, extension_id: str | None, submenus: list[str] | None) -> bool:
|
|
if not inx_path.exists():
|
|
print(f"ERROR: expected file not found: {inx_path}")
|
|
sys.exit(1)
|
|
|
|
print(f"Patching {inx_path}")
|
|
|
|
tree = ET.parse(inx_path)
|
|
root = tree.getroot()
|
|
|
|
modified = False
|
|
|
|
if extension_id is not None:
|
|
if set_extension_id(root, extension_id):
|
|
modified = True
|
|
|
|
if submenus is not None:
|
|
if set_effects_submenu(root, submenus):
|
|
modified = True
|
|
|
|
if modified:
|
|
ET.indent(tree, space=" ")
|
|
tree.write(inx_path, encoding="UTF-8", xml_declaration=True)
|
|
|
|
content = inx_path.read_text(encoding="utf-8")
|
|
if not content.endswith("\n"):
|
|
inx_path.write_text(content + "\n", encoding="utf-8")
|
|
|
|
return modified
|
|
|
|
|
|
def process_repo(repo: dict, ext_dir: Path) -> None:
|
|
inx_list = repo.get("inx", [])
|
|
directory = repo["directory"]
|
|
|
|
for inx_config in inx_list:
|
|
inx_file = inx_config.get("file")
|
|
extension_id = inx_config.get("id")
|
|
submenus = inx_config.get("submenu")
|
|
|
|
if inx_file and (extension_id or submenus):
|
|
inx_path = ext_dir / directory / inx_file
|
|
patch_inx(inx_path, extension_id, submenus)
|
|
|
|
|
|
def main() -> None:
|
|
root = Path(__file__).resolve().parent.parent
|
|
ext_dir = root / "extensions"
|
|
ext_dir.mkdir(exist_ok=True)
|
|
|
|
os.chdir(root)
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
tmp_dir = Path(tmp)
|
|
|
|
for repo in REPOS:
|
|
sync_repo(repo, ext_dir, tmp_dir)
|
|
|
|
subprocess.run(["git", "add", "extensions"], check=True)
|
|
|
|
if not has_staged_changes(root):
|
|
print("No bundle changes.")
|
|
sys.exit(0)
|
|
|
|
subprocess.run(["git", "checkout", "extensions"], check=True)
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
tmp_dir = Path(tmp)
|
|
|
|
for repo in REPOS:
|
|
sync_repo(repo, ext_dir, tmp_dir)
|
|
process_repo(repo, ext_dir)
|
|
|
|
subprocess.run(["git", "add", "extensions"], check=True)
|
|
|
|
if not has_staged_changes(root):
|
|
print("No bundle changes.")
|
|
sys.exit(0)
|
|
|
|
date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
subprocess.run(
|
|
["git", "commit", "-m", f"bundle: update ({date_str})"],
|
|
check=True,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|