mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-11 03:23:51 +00:00
feat(editor): add --list-by-magic to filter a tree to one specific format
Walks a directory recursively and lists every file matching a 4-character magic. Reports per-file size, entry count, catalog name, and relative path; aggregates total bytes + total entries across the matches. Useful for narrow per-format inventory: "show me all the WSPL spell catalogs in my project" or "find every WCDF that defines raid difficulty variants under my custom-content tree". The existing --summary-dir gives a multi-format rollup; this is the single-format zoom-in. Magic argument is case-insensitive — "WSPL" and "wspl" both match. If the magic is recognized in the format table the report header includes the format description and category; if not (e.g. a server-custom magic), the listing still works and just notes "unknown magic — no metadata". JSON sidecar via --json. Exit 1 if no matches (so it composes into shell "if any matches, do X" pipelines). This is the 17th cross-format utility: --list-formats / --info-magic / --summary-dir / --rename-by-magic --bulk-rename-by-magic / --touch-tree / --tree-summary-md --catalog-grep / --diff-headers / --audit-tree / --magic-fix --bulk-validate / --bulk-export-json / --bulk-import-json --diff-tree / --orphan-jsons / --list-by-magic CLI flag count 1032 -> 1033.
This commit is contained in:
parent
14c6a947ee
commit
6abd3f5398
6 changed files with 197 additions and 1 deletions
|
|
@ -1483,6 +1483,7 @@ add_executable(wowee_editor
|
|||
tools/editor/cli_bulk_json.cpp
|
||||
tools/editor/cli_diff_tree.cpp
|
||||
tools/editor/cli_orphan_jsons.cpp
|
||||
tools/editor/cli_list_by_magic.cpp
|
||||
tools/editor/cli_macros_catalog.cpp
|
||||
tools/editor/cli_char_features_catalog.cpp
|
||||
tools/editor/cli_pvp_catalog.cpp
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ const char* const kArgRequired[] = {
|
|||
"--catalog-grep", "--diff-headers", "--audit-tree",
|
||||
"--magic-fix", "--bulk-validate",
|
||||
"--bulk-export-json", "--bulk-import-json",
|
||||
"--diff-tree", "--orphan-jsons",
|
||||
"--diff-tree", "--orphan-jsons", "--list-by-magic",
|
||||
"--gen-animations", "--gen-animations-combat", "--gen-animations-movement",
|
||||
"--info-wani", "--validate-wani",
|
||||
"--export-wani-json", "--import-wani-json",
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@
|
|||
#include "cli_bulk_json.hpp"
|
||||
#include "cli_diff_tree.hpp"
|
||||
#include "cli_orphan_jsons.hpp"
|
||||
#include "cli_list_by_magic.hpp"
|
||||
#include "cli_macros_catalog.hpp"
|
||||
#include "cli_char_features_catalog.hpp"
|
||||
#include "cli_pvp_catalog.hpp"
|
||||
|
|
@ -271,6 +272,7 @@ constexpr DispatchFn kDispatchTable[] = {
|
|||
handleBulkJson,
|
||||
handleDiffTree,
|
||||
handleOrphanJsons,
|
||||
handleListByMagic,
|
||||
handleMacrosCatalog,
|
||||
handleCharFeaturesCatalog,
|
||||
handlePVPCatalog,
|
||||
|
|
|
|||
|
|
@ -1379,6 +1379,8 @@ void printUsage(const char* argv0) {
|
|||
std::printf(" Compare two directory trees of .w* catalogs at the magic+size level. Reports only-in-A / only-in-B / magic-changed / size-changed / identical counts and lists changed paths. Exit 1 if any difference\n");
|
||||
std::printf(" --orphan-jsons <dir> [--json]\n");
|
||||
std::printf(" Find .wXXX.json sidecars whose binary .wXXX is missing. Useful after deleting/moving binaries — orphan JSONs accumulate noise and may shadow re-imports. Exit 1 if any orphans found\n");
|
||||
std::printf(" --list-by-magic <dir> <magic> [--json]\n");
|
||||
std::printf(" List every file in a directory tree matching a 4-char magic (e.g. WSPL). Reports per-file size + entry count + catalog name + relative path. Exit 1 if no matches\n");
|
||||
std::printf(" --gen-animations <wani-base> [name]\n");
|
||||
std::printf(" Emit .wani starter: 5 essential animations (Stand / Walk / Run / Death / AttackUnarmed) with fallback chains\n");
|
||||
std::printf(" --gen-animations-combat <wani-base> [name]\n");
|
||||
|
|
|
|||
180
tools/editor/cli_list_by_magic.cpp
Normal file
180
tools/editor/cli_list_by_magic.cpp
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
#include "cli_list_by_magic.hpp"
|
||||
#include "cli_arg_parse.hpp"
|
||||
#include "cli_format_table.hpp"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
namespace cli {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
struct Match {
|
||||
fs::path path;
|
||||
uintmax_t size = 0;
|
||||
uint32_t entryCount = 0;
|
||||
std::string catalogName;
|
||||
};
|
||||
|
||||
// Read magic + version + name + entryCount. Same shape as
|
||||
// the helper in cli_summary_dir — kept local to avoid
|
||||
// header-only utility ping-pong.
|
||||
bool peekHeader(const fs::path& path, char magic[4],
|
||||
std::string& nameOut, uint32_t& entryCountOut) {
|
||||
std::ifstream is(path, std::ios::binary);
|
||||
if (!is) return false;
|
||||
if (!is.read(magic, 4) || is.gcount() != 4) return false;
|
||||
uint32_t version = 0;
|
||||
if (!is.read(reinterpret_cast<char*>(&version), 4)) return false;
|
||||
uint32_t nameLen = 0;
|
||||
if (!is.read(reinterpret_cast<char*>(&nameLen), 4)) return false;
|
||||
if (nameLen > (1u << 20)) return false;
|
||||
nameOut.resize(nameLen);
|
||||
if (nameLen > 0) {
|
||||
is.read(nameOut.data(), nameLen);
|
||||
if (is.gcount() != static_cast<std::streamsize>(nameLen)) {
|
||||
nameOut.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!is.read(reinterpret_cast<char*>(&entryCountOut), 4))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Normalize a 4-character magic input. Accepts both
|
||||
// "WSPL" and "wspl" (case-insensitive uppercase).
|
||||
bool parseMagicArg(const char* arg, char magic[4]) {
|
||||
if (!arg) return false;
|
||||
size_t n = std::strlen(arg);
|
||||
if (n != 4) return false;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
char c = arg[i];
|
||||
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
|
||||
magic[i] = c;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int handleList(int& i, int argc, char** argv) {
|
||||
std::string dir = argv[++i];
|
||||
std::string magicArg = argv[++i];
|
||||
bool jsonOut = consumeJsonFlag(i, argc, argv);
|
||||
if (!fs::exists(dir) || !fs::is_directory(dir)) {
|
||||
std::fprintf(stderr,
|
||||
"list-by-magic: not a directory: %s\n", dir.c_str());
|
||||
return 1;
|
||||
}
|
||||
char wantedMagic[4];
|
||||
if (!parseMagicArg(magicArg.c_str(), wantedMagic)) {
|
||||
std::fprintf(stderr,
|
||||
"list-by-magic: magic must be exactly 4 characters: %s\n",
|
||||
magicArg.c_str());
|
||||
return 1;
|
||||
}
|
||||
const FormatMagicEntry* fmt = findFormatByMagic(wantedMagic);
|
||||
// fmt is allowed to be null — we still match files
|
||||
// with that magic, just won't have format metadata for
|
||||
// the report header.
|
||||
std::vector<Match> matches;
|
||||
uintmax_t totalBytes = 0;
|
||||
uint64_t totalEntries = 0;
|
||||
for (const auto& entry : fs::recursive_directory_iterator(dir)) {
|
||||
if (!entry.is_regular_file()) continue;
|
||||
char magic[4];
|
||||
std::string nameField;
|
||||
uint32_t entryCount = 0;
|
||||
if (!peekHeader(entry.path(), magic, nameField, entryCount))
|
||||
continue;
|
||||
if (std::memcmp(magic, wantedMagic, 4) != 0) continue;
|
||||
Match m;
|
||||
m.path = entry.path();
|
||||
m.size = entry.file_size();
|
||||
m.entryCount = entryCount;
|
||||
m.catalogName = nameField;
|
||||
totalBytes += m.size;
|
||||
totalEntries += entryCount;
|
||||
matches.push_back(std::move(m));
|
||||
}
|
||||
if (jsonOut) {
|
||||
nlohmann::json j;
|
||||
j["dir"] = dir;
|
||||
char ms[5] = {wantedMagic[0], wantedMagic[1],
|
||||
wantedMagic[2], wantedMagic[3], 0};
|
||||
j["magic"] = ms;
|
||||
if (fmt) {
|
||||
j["format"] = fmt->extension;
|
||||
j["category"] = fmt->category;
|
||||
j["description"] = fmt->description;
|
||||
} else {
|
||||
j["format"] = nullptr;
|
||||
}
|
||||
j["count"] = matches.size();
|
||||
j["totalBytes"] = totalBytes;
|
||||
j["totalEntries"] = totalEntries;
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& m : matches) {
|
||||
arr.push_back({
|
||||
{"path", fs::relative(m.path, dir).string()},
|
||||
{"size", m.size},
|
||||
{"entryCount", m.entryCount},
|
||||
{"catalogName", m.catalogName},
|
||||
});
|
||||
}
|
||||
j["matches"] = arr;
|
||||
std::printf("%s\n", j.dump(2).c_str());
|
||||
return matches.empty() ? 1 : 0;
|
||||
}
|
||||
char ms[5] = {wantedMagic[0], wantedMagic[1],
|
||||
wantedMagic[2], wantedMagic[3], 0};
|
||||
std::printf("list-by-magic: '%s' in %s\n", ms, dir.c_str());
|
||||
if (fmt) {
|
||||
std::printf(" format : %s (%s, %s)\n",
|
||||
fmt->description, fmt->extension, fmt->category);
|
||||
} else {
|
||||
std::printf(" format : (unknown magic — no metadata)\n");
|
||||
}
|
||||
std::printf(" matches : %zu\n", matches.size());
|
||||
std::printf(" total : %llu bytes, %llu entries\n",
|
||||
static_cast<unsigned long long>(totalBytes),
|
||||
static_cast<unsigned long long>(totalEntries));
|
||||
if (matches.empty()) {
|
||||
std::printf(" no files with magic '%s' under %s\n",
|
||||
ms, dir.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::printf("\n");
|
||||
std::printf(" bytes entries catalogName path\n");
|
||||
for (const auto& m : matches) {
|
||||
std::printf(" %8llu %6u %-25s %s\n",
|
||||
static_cast<unsigned long long>(m.size),
|
||||
m.entryCount,
|
||||
m.catalogName.c_str(),
|
||||
fs::relative(m.path, dir).string().c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool handleListByMagic(int& i, int argc, char** argv, int& outRc) {
|
||||
if (std::strcmp(argv[i], "--list-by-magic") == 0 && i + 2 < argc) {
|
||||
outRc = handleList(i, argc, argv); return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
11
tools/editor/cli_list_by_magic.hpp
Normal file
11
tools/editor/cli_list_by_magic.hpp
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
namespace cli {
|
||||
|
||||
bool handleListByMagic(int& i, int argc, char** argv, int& outRc);
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
Loading…
Add table
Add a link
Reference in a new issue