84 lines
3.0 KiB
Python
84 lines
3.0 KiB
Python
import os
|
|
import re
|
|
import sys
|
|
|
|
# File extensions to validate
|
|
TARGET_EXTS = {
|
|
".gd", # Godot scripts
|
|
".tscn", # Godot scenes
|
|
".tres", ".res", # resources
|
|
".png", ".jpg", # images
|
|
".glb", ".gltf", ".dae", ".obj", # 3D models
|
|
".wav", ".ogg", ".mp3", # sounds
|
|
}
|
|
|
|
# Directories to ignore entirely (e.g., external or tooling folders)
|
|
EXCLUDED_DIRS = {"addons", "script_templates", ".git", ".github", ".godot", "android"}
|
|
|
|
invalid_files = []
|
|
invalid_dirs = []
|
|
|
|
_BASE_PATTERN = re.compile(r"^[a-z0-9_]+$")
|
|
|
|
|
|
def to_snake_case(filename: str) -> str:
|
|
base, ext = os.path.splitext(filename)
|
|
s1 = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", base)
|
|
s2 = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1)
|
|
s3 = re.sub(r"[-\s]+", "_", s2)
|
|
return s3.lower() + ext
|
|
|
|
for root, dirs, files in os.walk('.', topdown=True):
|
|
rel_root = os.path.relpath(root, '.')
|
|
if any(
|
|
rel_root == excluded or rel_root.startswith(f"{excluded}{os.sep}")
|
|
for excluded in EXCLUDED_DIRS
|
|
):
|
|
dirs[:] = []
|
|
continue
|
|
|
|
if rel_root != '.':
|
|
dirname = os.path.basename(root)
|
|
if not _BASE_PATTERN.match(dirname):
|
|
suggestion = to_snake_case(dirname)
|
|
if os.path.dirname(rel_root):
|
|
suggested_rel = os.path.join(os.path.dirname(rel_root), suggestion)
|
|
else:
|
|
suggested_rel = suggestion
|
|
invalid_dirs.append((rel_root, suggested_rel))
|
|
|
|
# Validate subdirectories
|
|
for dir in list(dirs):
|
|
rel_path = os.path.join(rel_root, dir) if rel_root != '.' else dir
|
|
if any(
|
|
rel_path == excluded or rel_path.startswith(f"{excluded}{os.sep}")
|
|
for excluded in EXCLUDED_DIRS
|
|
):
|
|
dirs.remove(dir)
|
|
continue
|
|
if not _BASE_PATTERN.match(dir):
|
|
suggestion = to_snake_case(dir)
|
|
suggested_rel = os.path.join(rel_root, suggestion) if rel_root != '.' else suggestion
|
|
invalid_dirs.append((rel_path, suggested_rel))
|
|
|
|
for name in files:
|
|
base, ext = os.path.splitext(name)
|
|
if ext in TARGET_EXTS:
|
|
if not _BASE_PATTERN.match(base):
|
|
suggestion = to_snake_case(name)
|
|
rel_path = os.path.join(rel_root, name) if rel_root != '.' else name
|
|
suggested_rel = os.path.join(rel_root, suggestion) if rel_root != '.' else suggestion
|
|
invalid_files.append((rel_path, suggested_rel))
|
|
|
|
if invalid_dirs or invalid_files:
|
|
count = len(invalid_dirs) + len(invalid_files)
|
|
print("### \u274c Godot File Naming Convention Check Failed\n")
|
|
print(f"Script names must be in snake_case.\n{count} invalid name{'s' if count > 1 else ''} detected:\n")
|
|
for path, suggestion in invalid_dirs:
|
|
print(f"- **Directory** `{path}` \u2192 `{suggestion}`")
|
|
for path, suggestion in invalid_files:
|
|
print(f"- **File** `{path}` \u2192 `{suggestion}`")
|
|
sys.exit(1)
|
|
else:
|
|
print("\u2705 All filenames and directories follow Godot naming convention.")
|