106 lines
4.8 KiB
Python
106 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Renders a mod's Gradle/NeoForge project skeleton from a resolved JSON config.
|
|
|
|
This script is intentionally "dumb": all version resolution and validation
|
|
happens on the Nix side (lib/mkMod.nix). Given a flat JSON context and a
|
|
directory of Jinja2 templates, it renders a fixed manifest of files plus one
|
|
set of files per declared block into an output directory.
|
|
|
|
Generated code always lives under a "generated" subpackage, kept separate
|
|
from hand-written code (which lib/mkMod.nix symlinks in directly from the
|
|
mod's own src/ directory) so regenerating never clobbers hand-written files.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from jinja2 import Environment, FileSystemLoader
|
|
|
|
from . import blockbuilder
|
|
|
|
# (template file relative to --templates, output path relative to --out)
|
|
# Output paths may reference {package_path}, filled in from the config
|
|
# before rendering.
|
|
STATIC_MANIFEST = [
|
|
("build.gradle.jinja", "build.gradle"),
|
|
("settings.gradle.jinja", "settings.gradle"),
|
|
("gradle.properties.jinja", "gradle.properties"),
|
|
("gitignore.jinja", ".gitignore"),
|
|
("neoforge.mods.toml.jinja", "src/main/resources/META-INF/neoforge.mods.toml"),
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--config", required=True, type=Path, help="Path to the resolved mod config JSON")
|
|
parser.add_argument("--templates", required=True, type=Path, help="Directory of .jinja templates for the target MC version")
|
|
parser.add_argument("--out", required=True, type=Path, help="Output directory to render the project into")
|
|
args = parser.parse_args()
|
|
|
|
context = json.loads(args.config.read_text())
|
|
context["blocks"] = [blockbuilder.resolve_block(b) for b in context["blocks"]]
|
|
context["blockPropertyImports"] = sorted({
|
|
imp for block in context["blocks"] for imp in block["propertyImports"]
|
|
})
|
|
|
|
package_path = context["javaPackage"].replace(".", "/")
|
|
|
|
env = Environment(
|
|
loader=FileSystemLoader(str(args.templates)),
|
|
keep_trailing_newline=True,
|
|
)
|
|
|
|
def render(template_name: str, output_relpath: str, **extra) -> None:
|
|
template = env.get_template(template_name)
|
|
rendered = template.render(**context, **extra)
|
|
output_path = args.out / output_relpath
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
output_path.write_text(rendered)
|
|
|
|
for template_name, output_template in STATIC_MANIFEST:
|
|
render(template_name, output_template.format(package_path=package_path))
|
|
|
|
# Main mod class: flat + concrete when there's no hand-written override,
|
|
# otherwise an unannotated, abstract Base class under generated/.
|
|
if context["customClassName"]:
|
|
render("MainMod.java.jinja", f"src/main/java/{package_path}/generated/{context['className']}Base.java")
|
|
else:
|
|
render("MainMod.java.jinja", f"src/main/java/{package_path}/{context['className']}.java")
|
|
|
|
# Client-only class: only generated when the mod actually declares one.
|
|
if context["customClientClassName"]:
|
|
render("ClientMod.java.jinja", f"src/main/java/{package_path}/generated/{context['clientClassName']}Base.java")
|
|
|
|
# Block/block-entity registries: always generated (even empty), so the main
|
|
# class can unconditionally call their register(modEventBus) helpers.
|
|
render("Blocks.java.jinja", f"src/main/java/{package_path}/generated/blocks/{context['blocksClassName']}.java")
|
|
render("BlockEntities.java.jinja", f"src/main/java/{package_path}/generated/blocks/{context['blockEntitiesClassName']}.java")
|
|
|
|
for block in context["blocks"]:
|
|
block_file_name = ("Base" + block["concreteClassName"]) if block["customClassName"] else block["concreteClassName"]
|
|
render("Block.java.jinja", f"src/main/java/{package_path}/generated/blocks/{block_file_name}.java", block=block)
|
|
|
|
if block["blockEntity"] is not None:
|
|
be = block["blockEntity"]
|
|
be_file_name = ("Base" + be["concreteClassName"]) if be["customClassName"] else be["concreteClassName"]
|
|
render("BlockEntity.java.jinja", f"src/main/java/{package_path}/generated/blocks/{be_file_name}.java", block=block)
|
|
|
|
# blockstates.json always references "<modid>:block/<path>" regardless of
|
|
# whether that model is generated below or hand-written (customModel).
|
|
assets_dir = f"src/main/resources/assets/{context['modid']}"
|
|
render("blockstates.json.jinja", f"{assets_dir}/blockstates/{block['path']}.json", block=block)
|
|
|
|
if not block["customModel"]:
|
|
render("BlockModel.json.jinja", f"{assets_dir}/models/block/{block['path']}.json", block=block)
|
|
|
|
if block["item"]["enabled"] and not block["item"]["customModel"]:
|
|
render("ItemModel.json.jinja", f"{assets_dir}/models/item/{block['path']}.json", block=block)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|