mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-08 01:53:52 +00:00
feat(editor): add --remove-project-orphans for pre-pack cleanup
Destructive companion to --list-project-orphans. Reuses the same reference-collection + orphan-detection logic, then deletes the resulting .wom/.wob files. Honors --dry-run for safe previews. Completes the list/remove cycle for unreferenced model files: run --list-project-orphans to audit, then --remove-project-orphans --dry-run to confirm, then drop --dry-run to actually clean. Useful right before --pack-wcp so the archive doesn't carry dead weight. Verified: dry-run preserves files (3 reported, all still present); real run on a copy removes all 3 with no failures, freeing 1.2 KB. Brings command count to 182.
This commit is contained in:
parent
0eb20a4069
commit
f1bd7b7f1f
1 changed files with 125 additions and 1 deletions
|
|
@ -525,6 +525,8 @@ static void printUsage(const char* argv0) {
|
|||
std::printf(" ASCII-render the 64x64 WoW ADT grid showing tile claims by zone\n");
|
||||
std::printf(" --list-project-orphans <projectDir> [--json]\n");
|
||||
std::printf(" Find .wom/.wob files in zones not referenced by any objects.json or doodad list\n");
|
||||
std::printf(" --remove-project-orphans <projectDir> [--dry-run]\n");
|
||||
std::printf(" Delete the orphan .wom/.wob files surfaced by --list-project-orphans\n");
|
||||
std::printf(" --list-zone-deps <zoneDir> [--json]\n");
|
||||
std::printf(" List external M2/WMO model paths a zone references (objects + WOB doodads)\n");
|
||||
std::printf(" --export-zone-deps-md <zoneDir> [out.md]\n");
|
||||
|
|
@ -896,7 +898,7 @@ int main(int argc, char* argv[]) {
|
|||
"--validate-project-checksum",
|
||||
"--scaffold-zone", "--mvp-zone", "--add-tile", "--remove-tile", "--list-tiles",
|
||||
"--for-each-zone", "--for-each-tile", "--zone-stats", "--info-tilemap",
|
||||
"--list-zone-deps", "--list-project-orphans",
|
||||
"--list-zone-deps", "--list-project-orphans", "--remove-project-orphans",
|
||||
"--check-zone-refs", "--check-zone-content",
|
||||
"--check-project-content", "--check-project-refs",
|
||||
"--export-zone-deps-md", "--export-zone-spawn-png",
|
||||
|
|
@ -14219,6 +14221,128 @@ int main(int argc, char* argv[]) {
|
|||
o.path.c_str());
|
||||
}
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--remove-project-orphans") == 0 && i + 1 < argc) {
|
||||
// Destructive companion to --list-project-orphans. Reuses
|
||||
// the same reference-collection + orphan-detection logic
|
||||
// and then deletes the resulting files. --dry-run shows
|
||||
// what would be removed without touching anything.
|
||||
std::string projectDir = argv[++i];
|
||||
bool dryRun = false;
|
||||
if (i + 1 < argc && std::strcmp(argv[i + 1], "--dry-run") == 0) {
|
||||
dryRun = true; i++;
|
||||
}
|
||||
namespace fs = std::filesystem;
|
||||
if (!fs::exists(projectDir) || !fs::is_directory(projectDir)) {
|
||||
std::fprintf(stderr,
|
||||
"remove-project-orphans: %s is not a directory\n",
|
||||
projectDir.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::vector<std::string> zones;
|
||||
for (const auto& entry : fs::directory_iterator(projectDir)) {
|
||||
if (!entry.is_directory()) continue;
|
||||
if (!fs::exists(entry.path() / "zone.json")) continue;
|
||||
zones.push_back(entry.path().string());
|
||||
}
|
||||
std::sort(zones.begin(), zones.end());
|
||||
// Same normalize + reference collection as --list-project-orphans.
|
||||
// Keep both functions in sync if the matching rules evolve.
|
||||
auto normalize = [](std::string p) {
|
||||
while (p.size() >= 2 && p[0] == '.' && p[1] == '/') p.erase(0, 2);
|
||||
std::string ext = fs::path(p).extension().string();
|
||||
if (ext == ".wom" || ext == ".wob" || ext == ".m2" || ext == ".wmo") {
|
||||
p = p.substr(0, p.size() - ext.size());
|
||||
}
|
||||
return p;
|
||||
};
|
||||
std::set<std::string> referencedBases;
|
||||
for (const auto& zoneDir : zones) {
|
||||
wowee::editor::ObjectPlacer op;
|
||||
if (op.loadFromFile(zoneDir + "/objects.json")) {
|
||||
for (const auto& o : op.getObjects()) {
|
||||
if (o.path.empty()) continue;
|
||||
std::string norm = normalize(o.path);
|
||||
referencedBases.insert(norm);
|
||||
referencedBases.insert(fs::path(norm).filename().string());
|
||||
}
|
||||
}
|
||||
std::error_code ec;
|
||||
for (const auto& e : fs::recursive_directory_iterator(zoneDir, ec)) {
|
||||
if (!e.is_regular_file()) continue;
|
||||
if (e.path().extension() != ".wob") continue;
|
||||
std::string base = e.path().string();
|
||||
if (base.size() >= 4) base = base.substr(0, base.size() - 4);
|
||||
auto bld = wowee::pipeline::WoweeBuildingLoader::load(base);
|
||||
for (const auto& d : bld.doodads) {
|
||||
if (d.modelPath.empty()) continue;
|
||||
std::string norm = normalize(d.modelPath);
|
||||
referencedBases.insert(norm);
|
||||
referencedBases.insert(fs::path(norm).filename().string());
|
||||
}
|
||||
}
|
||||
}
|
||||
int removed = 0, failed = 0;
|
||||
uint64_t freedBytes = 0;
|
||||
for (const auto& zoneDir : zones) {
|
||||
std::string zoneName = fs::path(zoneDir).filename().string();
|
||||
std::error_code ec;
|
||||
std::vector<fs::path> toRemove;
|
||||
for (const auto& e : fs::recursive_directory_iterator(zoneDir, ec)) {
|
||||
if (!e.is_regular_file()) continue;
|
||||
std::string ext = e.path().extension().string();
|
||||
if (ext != ".wom" && ext != ".wob") continue;
|
||||
std::string rel = fs::relative(e.path(), zoneDir, ec).string();
|
||||
if (ec) rel = e.path().filename().string();
|
||||
std::string normRel = rel.substr(0, rel.size() - ext.size());
|
||||
std::string leaf = e.path().stem().string();
|
||||
if (referencedBases.count(normRel) ||
|
||||
referencedBases.count(leaf)) continue;
|
||||
toRemove.push_back(e.path());
|
||||
}
|
||||
// Materialize the deletion list before removing so we
|
||||
// don't mutate the directory while iterating.
|
||||
for (const auto& p : toRemove) {
|
||||
uint64_t sz = fs::file_size(p, ec);
|
||||
if (ec) sz = 0;
|
||||
std::string rel = fs::relative(p, zoneDir, ec).string();
|
||||
if (ec) rel = p.filename().string();
|
||||
if (dryRun) {
|
||||
std::printf(" would remove: %s/%s (%llu bytes)\n",
|
||||
zoneName.c_str(), rel.c_str(),
|
||||
static_cast<unsigned long long>(sz));
|
||||
removed++;
|
||||
freedBytes += sz;
|
||||
} else {
|
||||
if (fs::remove(p, ec)) {
|
||||
std::printf(" removed: %s/%s (%llu bytes)\n",
|
||||
zoneName.c_str(), rel.c_str(),
|
||||
static_cast<unsigned long long>(sz));
|
||||
removed++;
|
||||
freedBytes += sz;
|
||||
} else {
|
||||
std::fprintf(stderr,
|
||||
" WARN: failed to remove %s (%s)\n",
|
||||
p.c_str(), ec.message().c_str());
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::printf("\nremove-project-orphans: %s%s\n",
|
||||
projectDir.c_str(), dryRun ? " (dry-run)" : "");
|
||||
std::printf(" zones : %zu\n", zones.size());
|
||||
std::printf(" refs : %zu (normalized basenames)\n",
|
||||
referencedBases.size());
|
||||
std::printf(" %s : %d file(s)\n",
|
||||
dryRun ? "would remove" : "removed ", removed);
|
||||
std::printf(" freed : %.1f KB\n", freedBytes / 1024.0);
|
||||
if (failed > 0) {
|
||||
std::printf(" FAILED : %d (see stderr)\n", failed);
|
||||
}
|
||||
if (dryRun && removed > 0) {
|
||||
std::printf(" re-run without --dry-run to apply\n");
|
||||
}
|
||||
return failed == 0 ? 0 : 1;
|
||||
} else if (std::strcmp(argv[i], "--export-zone-deps-md") == 0 && i + 1 < argc) {
|
||||
// Markdown counterpart to --list-zone-deps. Writes a sortable
|
||||
// GitHub-rendered table of every external model the zone
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue