Autism-Client/addon-templates/addon-toolkit.py

760 lines
28 KiB
Python
Raw Normal View History

2026-09-07 00:11:44 +02:00
#!/usr/bin/env python3
"""AUTISM addon toolkit: scaffold, scan, validate and build addons. Runs anywhere with Python 3.
python addon-toolkit.py # menu
python addon-toolkit.py setup --template minimal --name "My Addon" --output ../MyAddon
python addon-toolkit.py scan --project ../MyAddon
python addon-toolkit.py build --project ../MyAddon
"""
from __future__ import annotations
import argparse
import json
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
REPO_ROOT = SCRIPT_DIR.parent
TEMPLATES = ("minimal", "advanced")
TEXT_EXTS = {".java", ".json", ".kts", ".toml", ".properties", ".md", ".yml", ".yaml"}
class ToolkitError(Exception):
"""Expected failure, shown without a traceback."""
def info(msg: str) -> None: print(f"[AUTISM] {msg}")
def ok(msg: str) -> None: print(f"[OK] {msg}")
def warn(msg: str) -> None: print(f"[WARN] {msg}")
def bad(msg: str) -> None: print(f"[FAIL] {msg}")
def path_line(label: str, path) -> None:
print(f"{label}:")
print(str(path))
def failure_item(msg: str) -> None:
if re.match(r"^[A-Za-z]:[\\/]", msg) or msg.startswith("/") or re.match(r"^\.{1,2}[\\/]", msg):
path_line("Path", msg)
else:
bad(f" - {msg}")
def is_interactive(args) -> bool:
return not getattr(args, "non_interactive", False) and sys.stdin.isatty()
def read_text(path: Path) -> str:
return path.read_text(encoding="utf-8")
def write_text(path: Path, text: str) -> None:
path.write_text(text, encoding="utf-8", newline="")
def to_folder_name(name: str) -> str:
if not name or not name.strip():
return "MyAddon"
parts = re.findall(r"[A-Za-z0-9]+", name)
if not parts:
return "MyAddon"
folder = "".join(p.upper() if len(p) == 1 else p[0].upper() + p[1:] for p in parts)
return folder[:48]
def to_package_segment(value: str) -> str:
segment = re.sub(r"[^a-z0-9]+", "", value.lower())
if not segment:
segment = "myaddon"
if not segment[0].isalpha():
segment = "addon" + segment
return segment
def to_mod_id(name: str) -> str:
mod_id = re.sub(r"[^a-z0-9_-]+", "-", name.lower()).strip("-_")
if not mod_id:
return "my-autism-addon"
if not mod_id[0].isalpha():
mod_id = "addon-" + mod_id
if len(mod_id) > 64:
mod_id = mod_id[:64].strip("-_")
return mod_id
def default_author() -> str:
return os.environ.get("USERNAME") or os.environ.get("USER") or "You"
def test_mod_id(value: str) -> bool:
return bool(re.match(r"^[a-z][a-z0-9_-]{1,63}$", value or ""))
def test_java_package(value: str) -> bool:
return bool(re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)+$", value or ""))
def test_version(value: str) -> bool:
return bool(re.match(r"^[0-9A-Za-z][0-9A-Za-z._+-]*$", value or ""))
def step_version(version: str) -> str:
if not version or not version.strip():
return "1.0.0"
m = re.match(r"^(.*?)(\d+)(\D*)$", version)
if not m:
return version + ".1"
prefix, digits, suffix = m.group(1), m.group(2), m.group(3)
nxt = str(int(digits) + 1)
if len(digits) > 1 and len(nxt) < len(digits):
nxt = nxt.rjust(len(digits), "0")
return f"{prefix}{nxt}{suffix}"
def get_toml_version(path: Path, key: str) -> str:
m = re.search(r'(?m)^\s*' + re.escape(key) + r'\s*=\s*"([^"]+)"', read_text(path))
return m.group(1) if m else ""
def set_toml_version(path: Path, key: str, value: str) -> None:
text = read_text(path)
pattern = r'(?m)^(\s*' + re.escape(key) + r'\s*=\s*)"[^"]*"'
if re.search(pattern, text):
text = re.sub(pattern, lambda m: m.group(1) + f'"{value}"', text)
else:
text += f'\n{key} = "{value}"\n'
write_text(path, text)
def set_properties_value(path: Path, key: str, value: str) -> None:
text = read_text(path)
pattern = r"(?m)^" + re.escape(key) + r"=.*$"
if re.search(pattern, text):
text = re.sub(pattern, f"{key}={value}", text)
else:
text += f"\n{key}={value}\n"
write_text(path, text)
def set_root_project_name(path: Path, name: str) -> None:
text = read_text(path)
pattern = r'(?m)^(rootProject\.name\s*=\s*)"[^"]*"'
if re.search(pattern, text):
text = re.sub(pattern, lambda m: m.group(1) + f'"{name}"', text)
else:
text += f'\nrootProject.name = "{name}"\n'
write_text(path, text)
def get_json(path: Path):
return json.loads(read_text(path))
def save_json(path: Path, data) -> None:
write_text(path, json.dumps(data, indent=2, ensure_ascii=False) + "\n")
def is_under_build_or_gradle(path: Path, project: Path) -> bool:
try:
parts = path.relative_to(project).parts
except ValueError:
return False
return "build" in parts or ".gradle" in parts
def resolve_full_path(path: str) -> Path | None:
if not path or not path.strip():
return None
p = Path(path)
if p.is_absolute():
return p.resolve()
return (REPO_ROOT / p).resolve()
def is_shipped_template(path: Path) -> bool:
full = path.resolve()
return any(full == (REPO_ROOT / "addon-templates" / name).resolve() for name in TEMPLATES)
def get_first_java_package(project: Path) -> str:
entry = project / "src/main/resources/fabric.mod.json"
if entry.is_file():
data = get_json(entry)
classes = []
eps = data.get("entrypoints", {}) if isinstance(data, dict) else {}
for kind in ("client", "autism"):
if eps.get(kind):
classes += list(eps[kind]) if isinstance(eps[kind], list) else [eps[kind]]
for cls in classes:
m = re.match(r"^(.+)\.[^.]+$", str(cls))
if m:
return m.group(1)
java_root = project / "src/main/java"
if java_root.is_dir():
for java in sorted(java_root.rglob("*.java")):
m = re.search(r"(?m)^\s*package\s+([^;]+);", read_text(java))
if m:
return m.group(1).strip()
return "com.example.addon"
def class_simple_name(class_name: str, fallback: str) -> str:
m = re.search(r"\.([^.]+)$", class_name or "")
return m.group(1) if m else fallback
def replace_in_project_files(project: Path, old: str, new: str) -> None:
if not old or old == new:
return
for file in project.rglob("*"):
if not file.is_file() or file.suffix not in TEXT_EXTS:
continue
if is_under_build_or_gradle(file, project):
continue
text = read_text(file)
if old in text:
write_text(file, text.replace(old, new))
def move_java_package(project: Path, old_pkg: str, new_pkg: str) -> None:
if old_pkg == new_pkg:
return
src_root = project / "src/main/java"
old_path = src_root / old_pkg.replace(".", os.sep)
new_path = src_root / new_pkg.replace(".", os.sep)
if not old_path.is_dir():
return
if new_path.exists():
warn("Package path already exists. Not moving files.")
path_line("Path", new_path)
return
new_path.parent.mkdir(parents=True, exist_ok=True)
shutil.move(str(old_path), str(new_path))
cursor = old_path.parent
while src_root in cursor.parents and not any(cursor.iterdir()):
parent = cursor.parent
cursor.rmdir()
cursor = parent
def copy_addon_template(template_name: str, target: Path) -> None:
source = REPO_ROOT / "addon-templates" / template_name
if not source.is_dir():
path_line("Missing template", source)
raise ToolkitError("Template not found.")
if target.exists():
warn("Output already exists. Editing it in place.")
path_line("Path", target)
return
target.mkdir(parents=True, exist_ok=True)
for item in source.rglob("*"):
if is_under_build_or_gradle(item, source):
continue
rel = item.relative_to(source)
dest = target / rel
if item.is_dir():
dest.mkdir(parents=True, exist_ok=True)
else:
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(item, dest)
def prompt(label: str, default: str, validator=None, interactive: bool = True) -> str:
if not interactive:
return default
while True:
suffix = f" [{default}]" if default else ""
try:
value = input(f"{label}{suffix}: ").strip()
except EOFError:
return default
if not value:
value = default
if validator is None or validator(value):
return value
warn("Bad value. Fix it.")
def prompt_version(current: str, interactive: bool) -> str:
base = current if current and current.strip() else "1.0.0"
nxt = step_version(base)
if not interactive:
return nxt
while True:
try:
value = input(f"Version current {base}, blank = {nxt}: ").strip()
except EOFError:
return nxt
if not value:
return nxt
if test_version(value):
return value
warn("Bad version. Fix it.")
def get_setup_project_path(args, name_for_output: str) -> Path:
if args.project and args.project.strip():
project = resolve_full_path(args.project)
if is_shipped_template(project):
raise ToolkitError("No. The shipped templates stay clean. Use --output to copy one, or pick a real addon folder.")
return project
output = args.output
if not output or not output.strip():
default_out = str((REPO_ROOT / ".." / to_folder_name(name_for_output)).resolve())
output = prompt("Output folder", default_out, lambda v: bool(v and v.strip()), is_interactive(args))
target = resolve_full_path(output)
if is_shipped_template(target):
raise ToolkitError("No. The shipped templates stay clean. Pick a different output folder.")
copy_addon_template(args.template, target)
return target
def configure_addon(args) -> Path:
interactive = is_interactive(args)
addon_name = args.addon_name
if not addon_name or not addon_name.strip():
default_name = "My Advanced Addon" if args.template == "advanced" else "My Addon"
addon_name = prompt("Addon name", default_name, lambda v: bool(v and v.strip()), interactive)
project = get_setup_project_path(args, addon_name)
if not project.is_dir():
path_line("Missing addon folder", project)
raise ToolkitError("Addon project not found.")
fabric_path = project / "src/main/resources/fabric.mod.json"
versions_path = project / "gradle/libs.versions.toml"
properties_path = project / "gradle.properties"
settings_path = project / "settings.gradle.kts"
for required in (fabric_path, versions_path, properties_path, settings_path):
if not required.is_file():
path_line("Missing file", required)
raise ToolkitError("Missing required addon file.")
fabric = get_json(fabric_path)
old_id = str(fabric.get("id", ""))
old_package = get_first_java_package(project)
old_version = get_toml_version(versions_path, "mod-version")
eps = fabric.get("entrypoints", {}) or {}
old_client = str((eps.get("client") or [f"{old_package}.Init"])[0])
old_addon = str((eps.get("autism") or [f"{old_package}.Addon"])[0])
original_mixin = str((fabric.get("mixins") or [""])[0])
client_simple = class_simple_name(old_client, "Init")
addon_simple = class_simple_name(old_addon, "Addon")
advanced = args.advanced
addon_id = args.addon_id
if not addon_id or not addon_id.strip():
suggested = to_mod_id(addon_name) if "template" in old_id else old_id
addon_id = prompt("Addon id", suggested, test_mod_id, interactive) if advanced else suggested
addon_version = args.addon_version
if not addon_version or not addon_version.strip():
addon_version = prompt_version(old_version or "1.0.0", interactive)
package = args.package
if not package or not package.strip():
suggested_pkg = f"com.{to_package_segment(addon_id)}.addon"
package = prompt("Java package", suggested_pkg, test_java_package, interactive) if advanced else suggested_pkg
maven_group = args.maven_group
if not maven_group or not maven_group.strip():
maven_group = prompt("Maven group", package, test_java_package, interactive) if advanced else package
archive_name = args.archive_name
if not archive_name or not archive_name.strip():
archive_name = prompt("Jar/archive base name", addon_id, test_mod_id, interactive) if advanced else addon_id
author = args.author
if not author or not author.strip():
author = prompt("Author", default_author(), lambda v: bool(v and v.strip()), interactive)
description = args.description
if not description or not description.strip():
suggested_desc = f"{addon_name} addon for AUTISM Client."
description = prompt("Description", suggested_desc, lambda v: bool(v and v.strip()), interactive) if advanced else suggested_desc
replace_in_project_files(project, old_package, package)
replace_in_project_files(project, old_id, addon_id)
move_java_package(project, old_package, package)
fabric = get_json(fabric_path)
fabric["id"] = addon_id
fabric["name"] = addon_name
fabric["version"] = "${version}"
fabric["description"] = description
fabric["authors"] = [author]
fabric.setdefault("entrypoints", {})
fabric["entrypoints"]["client"] = [f"{package}.{client_simple}"]
fabric["entrypoints"]["autism"] = [f"{package}.{addon_simple}"]
if fabric.get("mixins"):
old_mixin_name = original_mixin or str(fabric["mixins"][0])
new_mixin_name = f"{addon_id}.mixins.json"
resource_dir = project / "src/main/resources"
old_mixin = resource_dir / old_mixin_name
new_mixin = resource_dir / new_mixin_name
if old_mixin.is_file() and old_mixin != new_mixin:
shutil.move(str(old_mixin), str(new_mixin))
if new_mixin.is_file():
mixin_json = get_json(new_mixin)
mixin_json["package"] = f"{package}.mixin"
save_json(new_mixin, mixin_json)
fabric["mixins"] = [new_mixin_name]
save_json(fabric_path, fabric)
set_toml_version(versions_path, "mod-version", addon_version)
set_properties_value(properties_path, "maven_group", maven_group)
set_properties_value(properties_path, "archives_base_name", archive_name)
set_root_project_name(settings_path, addon_id)
ok("Addon rewritten.")
path_line("Addon folder", project)
print(f" id: {addon_id}")
print(f" name: {addon_name}")
print(f" version: {addon_version}")
print(f" package: {package}")
print()
print("Next:")
print(f" python addon-templates/addon-toolkit.py build --project \"{project}\"")
path_line("Jar folder", project / "build" / "libs")
return project
def test_addon_project(project: Path, allow_template_names: bool = False):
failures: list[str] = []
warnings: list[str] = []
if not project.is_dir():
failures.append("Project folder does not exist.")
failures.append(str(project))
return failures, warnings
required = [
"build.gradle.kts", "settings.gradle.kts", "gradle.properties",
"gradle/libs.versions.toml", "src/main/resources/fabric.mod.json",
]
for item in required:
if not (project / item).is_file():
failures.append(f"Missing {item}")
if not (project / "gradlew.bat").is_file() and not (project / "gradlew").is_file():
failures.append("Missing gradlew / gradlew.bat")
fabric_path = project / "src/main/resources/fabric.mod.json"
if fabric_path.is_file():
try:
fabric = get_json(fabric_path)
if not test_mod_id(str(fabric.get("id", ""))):
failures.append(f"fabric.mod.json id is invalid: {fabric.get('id')}")
if not str(fabric.get("name", "")).strip():
failures.append("fabric.mod.json name is empty.")
eps = fabric.get("entrypoints", {}) or {}
if not eps.get("autism"):
failures.append("Missing autism entrypoint in fabric.mod.json.")
else:
for entry in eps["autism"]:
cls = project / ("src/main/java/" + str(entry).replace(".", "/") + ".java")
if not cls.is_file():
failures.append(f"Autism entrypoint class file is missing: {entry}")
if not (fabric.get("depends", {}) or {}).get("autism"):
failures.append("fabric.mod.json must depend on autism.")
if not allow_template_names:
if re.search(r"template|example", str(fabric.get("id", ""))):
warnings.append("Addon id still looks like a template/example.")
if re.search(r"Template|Example", str(fabric.get("name", ""))):
warnings.append("Addon name still looks like a template/example.")
if "You" in (fabric.get("authors") or []):
warnings.append("Author is still 'You'.")
except json.JSONDecodeError as exc:
failures.append(f"fabric.mod.json is not valid JSON: {exc}")
versions_path = project / "gradle/libs.versions.toml"
if versions_path.is_file():
if not test_version(get_toml_version(versions_path, "mod-version")):
failures.append("Addon mod-version is missing or invalid.")
if not get_toml_version(versions_path, "autism").strip():
failures.append("AUTISM dependency version is missing from gradle/libs.versions.toml.")
if (project / ".gradle").exists():
warnings.append(".gradle cache exists locally. Ignored; do not upload it.")
if (project / "build").exists():
warnings.append("build output exists locally. Ignored; do not upload it.")
if (project / ".github").exists():
warnings.append(".github exists inside this addon project. Fine for a copied template, but it is not root repo CI unless you move it.")
leftovers = (
"autism-minimal-addon-template", "autism-advanced-addon-template",
"AUTISM Minimal Addon Template", "AUTISM Advanced Addon Template",
)
for file in project.rglob("*"):
if not file.is_file() or file.suffix not in TEXT_EXTS or is_under_build_or_gradle(file, project):
continue
text = read_text(file)
if re.search(r"[ÂÃâ]", text):
warnings.append(f"Mojibake-looking text in {file}")
if not allow_template_names:
for leftover in leftovers:
if leftover in text:
warnings.append(f"Template leftover '{leftover}' in {file}")
break
return failures, warnings
def get_addon_project_path(args) -> Path:
if args.project and args.project.strip():
return resolve_full_path(args.project)
if args.output and args.output.strip():
target = resolve_full_path(args.output)
copy_addon_template(args.template, target)
return target
return REPO_ROOT / "addon-templates" / args.template
def scan_addon(args) -> None:
project = get_addon_project_path(args)
failures, warnings = test_addon_project(project)
info("Scanned addon project.")
path_line("Addon folder", project)
for w in warnings:
warn(f"Heads up: {w}")
if failures:
bad("Scan found broken stuff:")
for f in failures:
failure_item(f)
raise ToolkitError("Scan failed.")
ok("Scan clean.")
def validate_addon_system(args) -> None:
failures: list[str] = []
warnings: list[str] = []
root_required = [
"src/main/java/autismclient/addons/AddonManager.java",
"src/main/java/autismclient/api/AutismAddon.java",
"src/main/java/autismclient/api/SimpleAddon.java",
"src/main/java/autismclient/api/AutismAddons.java",
"src/main/java/autismclient/api/ApiVersion.java",
"src/main/java/autismclient/api/module/SimpleModule.java",
"src/main/java/autismclient/api/macro/SimpleAction.java",
"src/main/java/autismclient/api/macro/SimpleCondition.java",
"src/main/java/autismclient/api/macro/MacroActionRegistry.java",
"src/main/java/autismclient/api/macro/MacroPresetRegistry.java",
"src/main/java/autismclient/api/hud/HudElements.java",
"src/main/java/autismclient/api/event/AddonEvents.java",
"src/main/java/autismclient/gui/screen/AutismAddonsScreen.java",
"src/main/java/autismclient/util/macro/MissingAddonAction.java",
"addon-templates/README.md",
"addon-templates/addon-toolkit.py",
"addon-templates/addon-toolkit.ps1",
"addon-templates/addon-toolkit.sh",
]
for f in root_required:
if not (REPO_ROOT / f).is_file():
failures.append(f"Missing {f}")
gitignore = REPO_ROOT / ".gitignore"
if gitignore.is_file():
ignore_text = read_text(gitignore)
for pattern in ("!/src/", "!/addon-templates/", "!/addon-templates/**",
"/addon-templates/**/build/", "/addon-templates/**/.gradle/", "/mc-src*/"):
if pattern not in ignore_text:
failures.append(f".gitignore is missing {pattern}")
else:
failures.append("Missing .gitignore")
main_version = get_toml_version(REPO_ROOT / "gradle/libs.versions.toml", "mod-version")
for template_name in TEMPLATES:
project = REPO_ROOT / "addon-templates" / template_name
t_failures, t_warnings = test_addon_project(project, allow_template_names=True)
failures += [f"{template_name} template: {x}" for x in t_failures]
warnings += [f"{template_name} template: {x}" for x in t_warnings]
target_version = get_toml_version(project / "gradle/libs.versions.toml", "autism")
if main_version and target_version and main_version != target_version:
failures.append(f"{template_name} template targets AUTISM {target_version}, main client is {main_version}.")
for w in warnings:
warn(f"Heads up: {w}")
if failures:
bad("Addon system is broken:")
for f in failures:
failure_item(f)
raise ToolkitError("Addon system validation failed.")
ok("Addon system clean.")
def run_gradle(project: Path, gradle_args: list[str]) -> None:
wrapper = project / ("gradlew.bat" if os.name == "nt" else "gradlew")
if not wrapper.is_file():
path_line("Missing Gradle wrapper", wrapper)
raise ToolkitError("Missing Gradle wrapper.")
cmd = [str(wrapper)] + gradle_args
if os.name != "nt" and not os.access(wrapper, os.X_OK):
cmd = ["sh", str(wrapper)] + gradle_args
result = subprocess.run(cmd, cwd=str(project))
if result.returncode != 0:
path_line("Gradle project", project)
raise ToolkitError(f"Gradle failed with exit code {result.returncode}.")
def publish_api(args) -> None:
info("Publishing client API to Maven Local.")
run_gradle(REPO_ROOT, ["publishToMavenLocal", "--no-daemon"])
ok("Client API published.")
def build_addon(args) -> None:
project = get_addon_project_path(args)
if not project.is_dir():
path_line("Addon folder", project)
raise ToolkitError("Wrong addon folder.")
if not (project / "gradlew.bat").is_file() and not (project / "gradlew").is_file():
path_line("Addon folder", project)
raise ToolkitError("No Gradle wrapper there. Wrong addon folder.")
if not args.no_publish:
publish_api(args)
info("Building addon.")
path_line("Addon folder", project)
run_gradle(project, ["build", "--no-daemon"])
ok("Addon built.")
path_line("Jar folder", project / "build" / "libs")
def build_all_templates(args) -> None:
if not args.no_publish:
publish_api(args)
for template_name in TEMPLATES:
project = REPO_ROOT / "addon-templates" / template_name
info(f"Building {template_name} template.")
run_gradle(project, ["build", "--no-daemon"])
ok(f"{template_name} template built.")
def clean_addon(args) -> None:
project = get_addon_project_path(args)
interactive = is_interactive(args)
for folder in (".gradle", "build"):
path = project / folder
if path.exists():
confirmed = args.yes or (interactive and input(f"Remove {path} ? Type YES: ").strip() == "YES")
if confirmed:
shutil.rmtree(path)
ok("Deleted local build junk.")
path_line("Path", path)
def menu(args) -> None:
last_path = ""
while True:
print()
print("AUTISM ADDON KIT")
print("1. New addon")
print("2. Build addon")
print("3. Scan addon")
print("4. Test shipped templates")
print("5. Publish client API")
print("6. Delete local build junk")
print("0. Leave")
try:
choice = input("> ").strip()
except EOFError:
return
try:
if choice == "1":
args.template = prompt("Template minimal/advanced", args.template, lambda v: v in TEMPLATES, True)
args.project = ""
args.output = ""
last_path = str(configure_addon(args))
elif choice == "2":
default_path = last_path or str((REPO_ROOT / ".." / to_folder_name("")).resolve())
args.project = prompt("Addon folder", default_path, lambda v: bool(v and v.strip()), True)
build_addon(args)
elif choice == "3":
default_path = last_path or str((REPO_ROOT / ".." / to_folder_name("")).resolve())
args.project = prompt("Addon folder", default_path, lambda v: bool(v and v.strip()), True)
scan_addon(args)
elif choice == "4":
validate_addon_system(args)
build_all_templates(args)
elif choice == "5":
publish_api(args)
elif choice == "6":
default_path = last_path or str((REPO_ROOT / ".." / to_folder_name("")).resolve())
args.project = prompt("Addon folder", default_path, lambda v: bool(v and v.strip()), True)
clean_addon(args)
elif choice == "0":
return
else:
warn("Bad choice.")
except ToolkitError as exc:
bad(str(exc))
try:
input("Press Enter. The error stays here until you do")
except EOFError:
return
def normalize_action(value: str) -> str:
v = value.strip().lower()
return {"publishapi": "publish-api", "buildall": "build-all"}.get(v, v)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="AUTISM addon toolkit.")
parser.add_argument("action", nargs="?", default="menu", type=normalize_action,
choices=["menu", "setup", "scan", "validate", "publish-api", "build", "build-all", "clean"])
parser.add_argument("--template", type=str.lower, choices=list(TEMPLATES), default="minimal")
parser.add_argument("--project", "--project-path", dest="project")
parser.add_argument("--output", "--output-path", dest="output")
parser.add_argument("--addon-id", dest="addon_id")
parser.add_argument("--name", "--addon-name", dest="addon_name")
parser.add_argument("--addon-version", dest="addon_version")
parser.add_argument("--package")
parser.add_argument("--author")
parser.add_argument("--description")
parser.add_argument("--maven-group", dest="maven_group")
parser.add_argument("--archive-name", dest="archive_name")
parser.add_argument("--advanced", action="store_true")
parser.add_argument("--no-publish", action="store_true", dest="no_publish")
parser.add_argument("--yes", action="store_true")
parser.add_argument("--non-interactive", action="store_true", dest="non_interactive")
parser.add_argument("--debug-stack", action="store_true", dest="debug_stack")
return parser
ACTIONS = {
"menu": menu,
"setup": configure_addon,
"scan": scan_addon,
"validate": validate_addon_system,
"publish-api": publish_api,
"build": build_addon,
"build-all": build_all_templates,
"clean": clean_addon,
}
def main(argv: list[str]) -> int:
args = build_parser().parse_args(argv)
try:
ACTIONS[args.action](args)
return 0
except ToolkitError as exc:
bad(str(exc))
if args.debug_stack:
raise
return 1
except KeyboardInterrupt:
print()
return 130
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))