refactor(editor): extract batch convert handlers into cli_convert.cpp

Moves the four bulk format-conversion handlers out of main.cpp:
  --convert-m2-batch    (M2 → WOM)
  --convert-wmo-batch   (WMO → WOB)
  --convert-blp-batch   (BLP → PNG)
  --convert-dbc-batch   (DBC → JSON)

These all share the same pattern: walk srcDir recursively for
files of the input extension and fan out to the single-file
--convert-* counterpart via subprocess (preserving the existing
per-file logic as the source of truth, no duplication).

Single-file converters (--convert-m2, --convert-wmo, etc.) and
the --migrate-* meta-commands still live in main.cpp; they're
in dedicated argv-rescan loops that need a different extraction
approach.

main.cpp drops 18,396 → 18,198 lines (-198). Behavior verified
by re-running --convert-blp-batch with a missing directory and
confirming the same error message.
This commit is contained in:
Kelsi 2026-05-09 02:25:05 -07:00
parent 0c1f873029
commit 98c7621e1d
4 changed files with 276 additions and 203 deletions

View file

@ -1311,6 +1311,7 @@ add_executable(wowee_editor
tools/editor/cli_mesh_edit.cpp
tools/editor/cli_wom_info.cpp
tools/editor/cli_format_validate.cpp
tools/editor/cli_convert.cpp
tools/editor/editor_app.cpp
tools/editor/editor_camera.cpp
tools/editor/editor_viewport.cpp

View file

@ -0,0 +1,249 @@
#include "cli_convert.hpp"
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <string>
#include <vector>
namespace wowee {
namespace editor {
namespace cli {
namespace {
int handleConvertM2Batch(int& i, int argc, char** argv) {
// Bulk M2→WOM conversion. Walks <srcDir> recursively for
// every .m2 file and re-invokes --convert-m2 per file via
// a child process so the existing single-file logic (with
// its AssetManager + skin-resolution bookkeeping) is reused
// verbatim. Reports per-file pass/fail and an aggregate
// summary.
//
// Designed to migrate an entire creature/world model dump
// in one go. Pair with --convert-blp-batch and --convert-
// wmo-batch to migrate a complete extracted Data tree.
std::string srcDir = argv[++i];
namespace fs = std::filesystem;
if (!fs::exists(srcDir) || !fs::is_directory(srcDir)) {
std::fprintf(stderr,
"convert-m2-batch: %s is not a directory\n",
srcDir.c_str());
return 1;
}
std::vector<std::string> m2Files;
std::error_code ec;
for (const auto& e : fs::recursive_directory_iterator(srcDir, ec)) {
if (!e.is_regular_file()) continue;
std::string ext = e.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(),
[](unsigned char c) { return std::tolower(c); });
if (ext != ".m2") continue;
m2Files.push_back(e.path().string());
}
std::sort(m2Files.begin(), m2Files.end());
std::printf("convert-m2-batch: %s\n", srcDir.c_str());
std::printf(" candidates : %zu .m2 file(s)\n", m2Files.size());
std::string self = argv[0];
int ok = 0, failed = 0;
for (const auto& m2 : m2Files) {
std::fflush(stdout);
std::string cmd = "\"" + self + "\" --convert-m2 \"" + m2 + "\"";
cmd += " >/dev/null 2>&1";
int rc = std::system(cmd.c_str());
if (rc == 0) {
ok++;
std::printf(" [ok] %s\n", m2.c_str());
} else {
failed++;
std::printf(" [FAIL] %s (rc=%d)\n", m2.c_str(), rc);
}
}
std::printf("\n summary : %d ok, %d failed (out of %zu)\n",
ok, failed, m2Files.size());
return failed == 0 ? 0 : 1;
}
int handleConvertWmoBatch(int& i, int argc, char** argv) {
// Bulk WMO→WOB conversion. Same orchestrator pattern as
// --convert-m2-batch: walks <srcDir> recursively, runs the
// existing single-file --convert-wmo per file.
//
// Skips group files (e.g. Stormwind_001.wmo) since the
// root WMO converter already pulls those in transitively.
// A WMO is a "group file" iff its stem ends in _NNN where
// NNN is a 3-digit integer.
std::string srcDir = argv[++i];
namespace fs = std::filesystem;
if (!fs::exists(srcDir) || !fs::is_directory(srcDir)) {
std::fprintf(stderr,
"convert-wmo-batch: %s is not a directory\n",
srcDir.c_str());
return 1;
}
auto isGroupFile = [](const std::string& stem) {
if (stem.size() < 5) return false;
if (stem[stem.size() - 4] != '_') return false;
for (int k = 1; k <= 3; ++k) {
if (!std::isdigit(static_cast<unsigned char>(
stem[stem.size() - k]))) return false;
}
return true;
};
std::vector<std::string> wmoFiles;
int skippedGroups = 0;
std::error_code ec;
for (const auto& e : fs::recursive_directory_iterator(srcDir, ec)) {
if (!e.is_regular_file()) continue;
std::string ext = e.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(),
[](unsigned char c) { return std::tolower(c); });
if (ext != ".wmo") continue;
std::string stem = e.path().stem().string();
if (isGroupFile(stem)) { skippedGroups++; continue; }
wmoFiles.push_back(e.path().string());
}
std::sort(wmoFiles.begin(), wmoFiles.end());
std::printf("convert-wmo-batch: %s\n", srcDir.c_str());
std::printf(" candidates : %zu root .wmo file(s) (skipped %d group file(s))\n",
wmoFiles.size(), skippedGroups);
std::string self = argv[0];
int ok = 0, failed = 0;
for (const auto& wmo : wmoFiles) {
std::fflush(stdout);
std::string cmd = "\"" + self + "\" --convert-wmo \"" + wmo + "\"";
cmd += " >/dev/null 2>&1";
int rc = std::system(cmd.c_str());
if (rc == 0) {
ok++;
std::printf(" [ok] %s\n", wmo.c_str());
} else {
failed++;
std::printf(" [FAIL] %s (rc=%d)\n", wmo.c_str(), rc);
}
}
std::printf("\n summary : %d ok, %d failed (out of %zu)\n",
ok, failed, wmoFiles.size());
return failed == 0 ? 0 : 1;
}
int handleConvertBlpBatch(int& i, int argc, char** argv) {
// Bulk BLP→PNG conversion. Walks <srcDir> recursively for
// every .blp file and re-invokes --convert-blp-png per
// file via a child process. The single-file converter
// writes the .png as a sidecar next to the source by
// default, so a batched run mirrors the standard "PNG
// sidecar everywhere" layout.
std::string srcDir = argv[++i];
namespace fs = std::filesystem;
if (!fs::exists(srcDir) || !fs::is_directory(srcDir)) {
std::fprintf(stderr,
"convert-blp-batch: %s is not a directory\n",
srcDir.c_str());
return 1;
}
std::vector<std::string> blpFiles;
std::error_code ec;
for (const auto& e : fs::recursive_directory_iterator(srcDir, ec)) {
if (!e.is_regular_file()) continue;
std::string ext = e.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(),
[](unsigned char c) { return std::tolower(c); });
if (ext != ".blp") continue;
blpFiles.push_back(e.path().string());
}
std::sort(blpFiles.begin(), blpFiles.end());
std::printf("convert-blp-batch: %s\n", srcDir.c_str());
std::printf(" candidates : %zu .blp file(s)\n", blpFiles.size());
std::string self = argv[0];
int ok = 0, failed = 0;
for (const auto& blp : blpFiles) {
std::fflush(stdout);
std::string cmd = "\"" + self + "\" --convert-blp-png \"" + blp + "\"";
cmd += " >/dev/null 2>&1";
int rc = std::system(cmd.c_str());
if (rc == 0) {
ok++;
std::printf(" [ok] %s\n", blp.c_str());
} else {
failed++;
std::printf(" [FAIL] %s (rc=%d)\n", blp.c_str(), rc);
}
}
std::printf("\n summary : %d ok, %d failed (out of %zu)\n",
ok, failed, blpFiles.size());
return failed == 0 ? 0 : 1;
}
int handleConvertDbcBatch(int& i, int argc, char** argv) {
// Bulk DBC→JSON conversion. Walks <srcDir> recursively for
// every .dbc file and re-invokes --convert-dbc-json per
// file. Each .json sidecar is written next to the source.
// Final commit in the four-format batch-converter set:
// m2/wmo/blp/dbc → wom/wob/png/json. Run all four to
// migrate an extracted Data tree end-to-end.
std::string srcDir = argv[++i];
namespace fs = std::filesystem;
if (!fs::exists(srcDir) || !fs::is_directory(srcDir)) {
std::fprintf(stderr,
"convert-dbc-batch: %s is not a directory\n",
srcDir.c_str());
return 1;
}
std::vector<std::string> dbcFiles;
std::error_code ec;
for (const auto& e : fs::recursive_directory_iterator(srcDir, ec)) {
if (!e.is_regular_file()) continue;
std::string ext = e.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(),
[](unsigned char c) { return std::tolower(c); });
if (ext != ".dbc") continue;
dbcFiles.push_back(e.path().string());
}
std::sort(dbcFiles.begin(), dbcFiles.end());
std::printf("convert-dbc-batch: %s\n", srcDir.c_str());
std::printf(" candidates : %zu .dbc file(s)\n", dbcFiles.size());
std::string self = argv[0];
int ok = 0, failed = 0;
for (const auto& dbc : dbcFiles) {
std::fflush(stdout);
std::string cmd = "\"" + self + "\" --convert-dbc-json \"" + dbc + "\"";
cmd += " >/dev/null 2>&1";
int rc = std::system(cmd.c_str());
if (rc == 0) {
ok++;
std::printf(" [ok] %s\n", dbc.c_str());
} else {
failed++;
std::printf(" [FAIL] %s (rc=%d)\n", dbc.c_str(), rc);
}
}
std::printf("\n summary : %d ok, %d failed (out of %zu)\n",
ok, failed, dbcFiles.size());
return failed == 0 ? 0 : 1;
}
} // namespace
bool handleConvert(int& i, int argc, char** argv, int& outRc) {
if (std::strcmp(argv[i], "--convert-m2-batch") == 0 && i + 1 < argc) {
outRc = handleConvertM2Batch(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--convert-wmo-batch") == 0 && i + 1 < argc) {
outRc = handleConvertWmoBatch(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--convert-blp-batch") == 0 && i + 1 < argc) {
outRc = handleConvertBlpBatch(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--convert-dbc-batch") == 0 && i + 1 < argc) {
outRc = handleConvertDbcBatch(i, argc, argv); return true;
}
return false;
}
} // namespace cli
} // namespace editor
} // namespace wowee

View file

@ -0,0 +1,22 @@
#pragma once
namespace wowee {
namespace editor {
namespace cli {
// Dispatch the batch format-conversion handlers:
// --convert-m2-batch (M2 → WOM)
// --convert-wmo-batch (WMO → WOB)
// --convert-blp-batch (BLP → PNG)
// --convert-dbc-batch (DBC → JSON)
//
// Each fans out to its single-file --convert-* counterpart via
// subprocess so the existing per-file logic stays the source of
// truth.
//
// Returns true if matched; outRc holds the exit code.
bool handleConvert(int& i, int argc, char** argv, int& outRc);
} // namespace cli
} // namespace editor
} // namespace wowee

View file

@ -12,6 +12,7 @@
#include "cli_mesh_edit.hpp"
#include "cli_wom_info.hpp"
#include "cli_format_validate.hpp"
#include "cli_convert.hpp"
#include "content_pack.hpp"
#include "npc_spawner.hpp"
#include "object_placer.hpp"
@ -457,6 +458,9 @@ int main(int argc, char* argv[]) {
if (wowee::editor::cli::handleFormatValidate(i, argc, argv, outRc)) {
return outRc;
}
if (wowee::editor::cli::handleConvert(i, argc, argv, outRc)) {
return outRc;
}
}
if (std::strcmp(argv[i], "--data") == 0 && i + 1 < argc) {
dataPath = argv[++i];
@ -14360,209 +14364,6 @@ int main(int argc, char* argv[]) {
std::printf(" pass --dry-run off to actually delete\n");
}
return totalFailed == 0 ? 0 : 1;
} else if (std::strcmp(argv[i], "--convert-m2-batch") == 0 && i + 1 < argc) {
// Bulk M2→WOM conversion. Walks <srcDir> recursively for
// every .m2 file and re-invokes --convert-m2 per file via
// a child process so the existing single-file logic (with
// its AssetManager + skin-resolution bookkeeping) is reused
// verbatim. Reports per-file pass/fail and an aggregate
// summary.
//
// Designed to migrate an entire creature/world model dump
// in one go. Pair with --convert-blp-batch and --convert-
// wmo-batch to migrate a complete extracted Data tree.
std::string srcDir = argv[++i];
namespace fs = std::filesystem;
if (!fs::exists(srcDir) || !fs::is_directory(srcDir)) {
std::fprintf(stderr,
"convert-m2-batch: %s is not a directory\n",
srcDir.c_str());
return 1;
}
std::vector<std::string> m2Files;
std::error_code ec;
for (const auto& e : fs::recursive_directory_iterator(srcDir, ec)) {
if (!e.is_regular_file()) continue;
std::string ext = e.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(),
[](unsigned char c) { return std::tolower(c); });
if (ext != ".m2") continue;
m2Files.push_back(e.path().string());
}
std::sort(m2Files.begin(), m2Files.end());
std::printf("convert-m2-batch: %s\n", srcDir.c_str());
std::printf(" candidates : %zu .m2 file(s)\n", m2Files.size());
std::string self = argv[0];
int ok = 0, failed = 0;
for (const auto& m2 : m2Files) {
std::fflush(stdout);
std::string cmd = "\"" + self + "\" --convert-m2 \"" + m2 + "\"";
cmd += " >/dev/null 2>&1";
int rc = std::system(cmd.c_str());
if (rc == 0) {
ok++;
std::printf(" [ok] %s\n", m2.c_str());
} else {
failed++;
std::printf(" [FAIL] %s (rc=%d)\n", m2.c_str(), rc);
}
}
std::printf("\n summary : %d ok, %d failed (out of %zu)\n",
ok, failed, m2Files.size());
return failed == 0 ? 0 : 1;
} else if (std::strcmp(argv[i], "--convert-wmo-batch") == 0 && i + 1 < argc) {
// Bulk WMO→WOB conversion. Same orchestrator pattern as
// --convert-m2-batch: walks <srcDir> recursively, runs the
// existing single-file --convert-wmo per file.
//
// Skips group files (e.g. Stormwind_001.wmo) since the
// root WMO converter already pulls those in transitively.
// A WMO is a "group file" iff its stem ends in _NNN where
// NNN is a 3-digit integer.
std::string srcDir = argv[++i];
namespace fs = std::filesystem;
if (!fs::exists(srcDir) || !fs::is_directory(srcDir)) {
std::fprintf(stderr,
"convert-wmo-batch: %s is not a directory\n",
srcDir.c_str());
return 1;
}
auto isGroupFile = [](const std::string& stem) {
if (stem.size() < 5) return false;
if (stem[stem.size() - 4] != '_') return false;
for (int k = 1; k <= 3; ++k) {
if (!std::isdigit(static_cast<unsigned char>(
stem[stem.size() - k]))) return false;
}
return true;
};
std::vector<std::string> wmoFiles;
int skippedGroups = 0;
std::error_code ec;
for (const auto& e : fs::recursive_directory_iterator(srcDir, ec)) {
if (!e.is_regular_file()) continue;
std::string ext = e.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(),
[](unsigned char c) { return std::tolower(c); });
if (ext != ".wmo") continue;
std::string stem = e.path().stem().string();
if (isGroupFile(stem)) { skippedGroups++; continue; }
wmoFiles.push_back(e.path().string());
}
std::sort(wmoFiles.begin(), wmoFiles.end());
std::printf("convert-wmo-batch: %s\n", srcDir.c_str());
std::printf(" candidates : %zu root .wmo file(s) (skipped %d group file(s))\n",
wmoFiles.size(), skippedGroups);
std::string self = argv[0];
int ok = 0, failed = 0;
for (const auto& wmo : wmoFiles) {
std::fflush(stdout);
std::string cmd = "\"" + self + "\" --convert-wmo \"" + wmo + "\"";
cmd += " >/dev/null 2>&1";
int rc = std::system(cmd.c_str());
if (rc == 0) {
ok++;
std::printf(" [ok] %s\n", wmo.c_str());
} else {
failed++;
std::printf(" [FAIL] %s (rc=%d)\n", wmo.c_str(), rc);
}
}
std::printf("\n summary : %d ok, %d failed (out of %zu)\n",
ok, failed, wmoFiles.size());
return failed == 0 ? 0 : 1;
} else if (std::strcmp(argv[i], "--convert-blp-batch") == 0 && i + 1 < argc) {
// Bulk BLP→PNG conversion. Walks <srcDir> recursively for
// every .blp file and re-invokes --convert-blp-png per
// file via a child process. The single-file converter
// writes the .png as a sidecar next to the source by
// default, so a batched run mirrors the standard "PNG
// sidecar everywhere" layout.
std::string srcDir = argv[++i];
namespace fs = std::filesystem;
if (!fs::exists(srcDir) || !fs::is_directory(srcDir)) {
std::fprintf(stderr,
"convert-blp-batch: %s is not a directory\n",
srcDir.c_str());
return 1;
}
std::vector<std::string> blpFiles;
std::error_code ec;
for (const auto& e : fs::recursive_directory_iterator(srcDir, ec)) {
if (!e.is_regular_file()) continue;
std::string ext = e.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(),
[](unsigned char c) { return std::tolower(c); });
if (ext != ".blp") continue;
blpFiles.push_back(e.path().string());
}
std::sort(blpFiles.begin(), blpFiles.end());
std::printf("convert-blp-batch: %s\n", srcDir.c_str());
std::printf(" candidates : %zu .blp file(s)\n", blpFiles.size());
std::string self = argv[0];
int ok = 0, failed = 0;
for (const auto& blp : blpFiles) {
std::fflush(stdout);
std::string cmd = "\"" + self + "\" --convert-blp-png \"" + blp + "\"";
cmd += " >/dev/null 2>&1";
int rc = std::system(cmd.c_str());
if (rc == 0) {
ok++;
std::printf(" [ok] %s\n", blp.c_str());
} else {
failed++;
std::printf(" [FAIL] %s (rc=%d)\n", blp.c_str(), rc);
}
}
std::printf("\n summary : %d ok, %d failed (out of %zu)\n",
ok, failed, blpFiles.size());
return failed == 0 ? 0 : 1;
} else if (std::strcmp(argv[i], "--convert-dbc-batch") == 0 && i + 1 < argc) {
// Bulk DBC→JSON conversion. Walks <srcDir> recursively for
// every .dbc file and re-invokes --convert-dbc-json per
// file. Each .json sidecar is written next to the source.
// Final commit in the four-format batch-converter set:
// m2/wmo/blp/dbc → wom/wob/png/json. Run all four to
// migrate an extracted Data tree end-to-end.
std::string srcDir = argv[++i];
namespace fs = std::filesystem;
if (!fs::exists(srcDir) || !fs::is_directory(srcDir)) {
std::fprintf(stderr,
"convert-dbc-batch: %s is not a directory\n",
srcDir.c_str());
return 1;
}
std::vector<std::string> dbcFiles;
std::error_code ec;
for (const auto& e : fs::recursive_directory_iterator(srcDir, ec)) {
if (!e.is_regular_file()) continue;
std::string ext = e.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(),
[](unsigned char c) { return std::tolower(c); });
if (ext != ".dbc") continue;
dbcFiles.push_back(e.path().string());
}
std::sort(dbcFiles.begin(), dbcFiles.end());
std::printf("convert-dbc-batch: %s\n", srcDir.c_str());
std::printf(" candidates : %zu .dbc file(s)\n", dbcFiles.size());
std::string self = argv[0];
int ok = 0, failed = 0;
for (const auto& dbc : dbcFiles) {
std::fflush(stdout);
std::string cmd = "\"" + self + "\" --convert-dbc-json \"" + dbc + "\"";
cmd += " >/dev/null 2>&1";
int rc = std::system(cmd.c_str());
if (rc == 0) {
ok++;
std::printf(" [ok] %s\n", dbc.c_str());
} else {
failed++;
std::printf(" [FAIL] %s (rc=%d)\n", dbc.c_str(), rc);
}
}
std::printf("\n summary : %d ok, %d failed (out of %zu)\n",
ok, failed, dbcFiles.size());
return failed == 0 ? 0 : 1;
} else if (std::strcmp(argv[i], "--migrate-data-tree") == 0 && i + 1 < argc) {
// End-to-end open-format migration. Runs all four bulk
// converters (m2/wmo/blp/dbc → wom/wob/png/json) in order