Skip to content

Instantly share code, notes, and snippets.

@I-No-oNe
Created April 15, 2026 19:08
Show Gist options
  • Select an option

  • Save I-No-oNe/077da4444b2a0325e3c4a39a83057ef8 to your computer and use it in GitHub Desktop.

Select an option

Save I-No-oNe/077da4444b2a0325e3c4a39a83057ef8 to your computer and use it in GitHub Desktop.
MixinUpdater.py By Claude for porting from mc 1.21.11 to 26.1
import os
import re
import json
# ── Helpers ───────────────────────────────────────────────────────────────────
def expected_class_name(mixin_target: str) -> str:
return f"Mixin{mixin_target}"
def find_mixin_json(root: str) -> list[str]:
"""Auto-discover *mixin*.json files under root."""
found = []
for dirpath, _, files in os.walk(root):
for f in files:
if "mixin" in f.lower() and f.endswith(".json"):
found.append(os.path.join(dirpath, f))
return found
def collect_registered_classes(json_paths: list[str]) -> set[str]:
"""
Return the set of bare class names (no package prefix) referenced
across all mixin JSON files, across the 'mixins', 'client', and 'server' keys.
"""
registered: set[str] = set()
for jp in json_paths:
try:
with open(jp, "r", encoding="utf-8") as f:
data = json.load(f)
except (json.JSONDecodeError, OSError):
continue
for key in ("mixins", "client", "server"):
for entry in data.get(key, []):
registered.add(entry.rsplit(".", 1)[-1])
return registered
# ── File-level fix ────────────────────────────────────────────────────────────
def fix_java_file(filepath: str) -> list[tuple[str, str]]:
"""
Parse a Java file, fix class names that don't match @Mixin(X.class) -> MixinX,
rename the file if needed, and return a list of (old_name, new_name) renames.
"""
with open(filepath, "r", encoding="utf-8") as f:
original = f.read()
lines = original.splitlines()
new_lines = lines[:]
renames: list[tuple[str, str]] = []
i = 0
while i < len(lines):
mixin_match = re.search(r'@Mixin\((\w+)\.class\)', lines[i].strip())
if mixin_match:
target = mixin_match.group(1)
expected = expected_class_name(target)
for j in range(i + 1, min(i + 4, len(lines))):
class_match = re.search(r'\bclass\s+(\w+)', lines[j])
if class_match:
actual = class_match.group(1)
if actual != expected:
print(f"\n [MISMATCH] line {j + 1}")
print(f" @Mixin target : {target}")
print(f" Class found : {actual}")
print(f" Expected : {expected}")
new_lines = [l.replace(actual, expected) for l in new_lines]
renames.append((actual, expected))
else:
print(f" [OK] line {j + 1}{actual} matches")
break
i += 1
new_content = "\n".join(new_lines)
if new_content != original:
with open(filepath, "w", encoding="utf-8") as f:
f.write(new_content)
# Rename the .java file itself if the class name changed
for old_name, new_name in renames:
basename = os.path.basename(filepath)
if old_name in basename:
new_basename = basename.replace(old_name, new_name)
new_filepath = os.path.join(os.path.dirname(filepath), new_basename)
os.rename(filepath, new_filepath)
print(f" ✔ File renamed : {basename} -> {new_basename}")
filepath = new_filepath
break
return renames
# ── JSON fix ──────────────────────────────────────────────────────────────────
def fix_mixin_json(json_path: str, all_renames: dict[str, str]):
"""Replace old class names with new names inside the mixin JSON."""
with open(json_path, "r", encoding="utf-8") as f:
data = json.load(f)
changed = False
def fix_list(lst: list) -> list:
nonlocal changed
result = []
for entry in lst:
parts = entry.rsplit(".", 1)
class_part = parts[-1]
prefix = parts[0] + "." if len(parts) == 2 else ""
if class_part in all_renames:
new_entry = prefix + all_renames[class_part]
print(f" [JSON] '{entry}' -> '{new_entry}'")
result.append(new_entry)
changed = True
else:
result.append(entry)
return result
for key in ("mixins", "client", "server"):
if key in data and isinstance(data[key], list):
data[key] = fix_list(data[key])
if changed:
with open(json_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
print(f" ✔ JSON updated : {json_path}")
else:
print(f" [JSON] No changes needed in {os.path.basename(json_path)}")
# ── Unused file deletion ──────────────────────────────────────────────────────
def delete_unused_java_files(java_files: list[str], json_paths: list[str], dry_run: bool = False):
"""
Delete any .java mixin file whose class name is not referenced in any
mixin JSON. Only files that contain an @Mixin annotation are considered
candidates — plain helper/utility Java files are left alone.
Returns the list of deleted (or would-be-deleted) file paths.
"""
if not json_paths:
print("\n[UNUSED] No JSON files available — skipping unused-file check.")
return []
registered = collect_registered_classes(json_paths)
deleted: list[str] = []
print(f"\n{'=' * 50}")
print("Checking for unused mixin Java files...\n")
for filepath in java_files:
# Only inspect files that actually declare a @Mixin — avoids deleting
# base classes, interfaces, or utilities that share the directory.
try:
with open(filepath, "r", encoding="utf-8") as f:
source = f.read()
except OSError:
continue
if "@Mixin(" not in source:
continue
# Extract the declared class name from the file
class_match = re.search(r'\bclass\s+(\w+)', source)
if not class_match:
continue
class_name = class_match.group(1)
if class_name not in registered:
tag = "[DRY-RUN]" if dry_run else "[DELETE]"
print(f" {tag} {filepath}")
print(f" Class '{class_name}' is not referenced in any mixin JSON.")
deleted.append(filepath)
if not dry_run:
os.remove(filepath)
print(f" ✔ Deleted.")
if not deleted:
print(" No unused mixin files found.")
return deleted
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
# 1. Java source directory
java_dir = input("Enter path to Java source directory: ").strip()
if not os.path.isdir(java_dir):
print(f"Error: '{java_dir}' is not a valid directory.")
return
# 2. Mixin JSON — auto-discover or manual
discovered = find_mixin_json(java_dir)
json_paths: list[str] = []
if discovered:
print(f"\nFound {len(discovered)} mixin JSON file(s):")
for idx, p in enumerate(discovered, 1):
print(f" [{idx}] {p}")
choice = input("Use these? (y / or enter a custom path): ").strip()
if choice.lower() == "y":
json_paths = discovered
elif os.path.isfile(choice):
json_paths = [choice]
else:
print("Skipping JSON update.")
else:
manual = input("No mixin JSON auto-detected. Enter path manually (or leave blank to skip): ").strip()
if manual and os.path.isfile(manual):
json_paths = [manual]
elif manual:
print(f"Warning: '{manual}' not found — skipping JSON update.")
# 3. Scan + fix Java files
java_files = []
for root, _, files in os.walk(java_dir):
for f in files:
if f.endswith(".java"):
java_files.append(os.path.join(root, f))
if not java_files:
print("No Java files found.")
return
print(f"\nScanning {len(java_files)} Java file(s)...\n" + "=" * 50)
all_renames: dict[str, str] = {}
for filepath in java_files:
print(f"\n{filepath}")
renames = fix_java_file(filepath)
for old, new in renames:
all_renames[old] = new
# 4. Fix JSON files
if json_paths and all_renames:
print(f"\n{'=' * 50}\nUpdating mixin JSON file(s)...\n")
for jp in json_paths:
fix_mixin_json(jp, all_renames)
# 5. Re-collect the current java file list (names may have changed after renaming)
java_files_after = []
for root, _, files in os.walk(java_dir):
for f in files:
if f.endswith(".java"):
java_files_after.append(os.path.join(root, f))
# 6. Dry-run preview of unused files, then confirm deletion
if json_paths:
would_delete = delete_unused_java_files(java_files_after, json_paths, dry_run=True)
if would_delete:
confirm = input(
f"\n{len(would_delete)} unused file(s) listed above. Delete them? (yes / no): "
).strip().lower()
if confirm == "yes":
delete_unused_java_files(java_files_after, json_paths, dry_run=False)
else:
print("Skipping deletion.")
# 7. Summary
print(f"\n{'=' * 50}")
if all_renames:
print(f"Done. {len(all_renames)} rename(s) applied:")
for old, new in all_renames.items():
print(f" {old} -> {new}")
else:
print("Done. No mismatches found — everything looks correct!")
if __name__ == "__main__":
main()
@TutlaMC

TutlaMC commented Apr 18, 2026

Copy link
Copy Markdown

W

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment