refactor console_commands: applyCheatToggle, parsers YAML al namespace anonim, buildHelp i append_csv
This commit is contained in:
+144
-151
@@ -426,7 +426,7 @@ static auto cmdSound(const std::vector<std::string>& args) -> std::string {
|
||||
|
||||
#ifdef _DEBUG
|
||||
// DEBUG [MODE [ON|OFF]|START [HERE|ROOM|POS|SCENE <name>]]
|
||||
static auto cmdDebug(const std::vector<std::string>& args) -> std::string { // NOLINT(readability-function-cognitive-complexity)
|
||||
static auto cmdDebug(const std::vector<std::string>& args) -> std::string {
|
||||
// --- START subcommands (START SCENE works from any scene) ---
|
||||
if (!args.empty() && args[0] == "START") {
|
||||
// START SCENE [<name>] — works from any scene
|
||||
@@ -525,7 +525,7 @@ static auto changeRoomWithEditor(const std::string& room_file) -> std::string {
|
||||
return std::string("Room: ") + room_file;
|
||||
}
|
||||
|
||||
static auto cmdRoom(const std::vector<std::string>& args) -> std::string { // NOLINT(readability-function-cognitive-complexity)
|
||||
static auto cmdRoom(const std::vector<std::string>& args) -> std::string {
|
||||
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
|
||||
if (args.empty()) { return "usage: room <num>|next|prev|left|right|up|down"; }
|
||||
|
||||
@@ -613,7 +613,7 @@ static auto cmdScene(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
|
||||
// EDIT [ON|OFF|REVERT]
|
||||
static auto cmdEdit(const std::vector<std::string>& args) -> std::string { // NOLINT(readability-function-cognitive-complexity)
|
||||
static auto cmdEdit(const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty()) {
|
||||
// Toggle: si está activo → off, si no → on
|
||||
if ((MapEditor::get() != nullptr) && MapEditor::get()->isActive()) {
|
||||
@@ -814,47 +814,38 @@ static auto cmdHide(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
|
||||
// CHEAT [subcomando]
|
||||
static auto cmdCheat(const std::vector<std::string>& args) -> std::string { // NOLINT(readability-function-cognitive-complexity)
|
||||
// Apply ON/OFF/toggle a un cheat binari. mode="" → toggle; "ON"/"OFF" → estableix; altra cosa → cadena buida (usage error).
|
||||
static auto applyCheatToggle(Options::Cheat::State& cheat, std::string_view mode, std::string_view label) -> std::string {
|
||||
using State = Options::Cheat::State;
|
||||
if (mode.empty()) {
|
||||
cheat = (cheat == State::ENABLED) ? State::DISABLED : State::ENABLED;
|
||||
} else if (mode == "ON") {
|
||||
if (cheat == State::ENABLED) { return std::string(label) + " already ON"; }
|
||||
cheat = State::ENABLED;
|
||||
} else if (mode == "OFF") {
|
||||
if (cheat == State::DISABLED) { return std::string(label) + " already OFF"; }
|
||||
cheat = State::DISABLED;
|
||||
} else {
|
||||
return {}; // sentinel: mode invàlid
|
||||
}
|
||||
return std::string(label) + " " + (cheat == State::ENABLED ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
static auto cmdCheat(const std::vector<std::string>& args) -> std::string {
|
||||
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
|
||||
if (args.empty()) { return "usage: cheat [infinite lives|invincibility]"; }
|
||||
|
||||
// CHEAT INFINITE LIVES [ON|OFF]
|
||||
if (args[0] == "INFINITE") {
|
||||
if (args.size() < 2 || args[1] != "LIVES") { return "usage: cheat infinite lives [on|off]"; }
|
||||
auto& cheat = Options::cheats.infinite_lives;
|
||||
using State = Options::Cheat::State;
|
||||
const std::vector<std::string> REST(args.begin() + 2, args.end());
|
||||
// cppcheck-suppress knownConditionTrueFalse -- cppcheck no infiere que REST puede estar vacío cuando args.size() == 2.
|
||||
if (REST.empty()) {
|
||||
cheat = (cheat == State::ENABLED) ? State::DISABLED : State::ENABLED;
|
||||
} else if (REST[0] == "ON") {
|
||||
if (cheat == State::ENABLED) { return "Infinite lives already ON"; }
|
||||
cheat = State::ENABLED;
|
||||
} else if (REST[0] == "OFF") {
|
||||
if (cheat == State::DISABLED) { return "Infinite lives already OFF"; }
|
||||
cheat = State::DISABLED;
|
||||
} else {
|
||||
return "usage: cheat infinite lives [on|off]";
|
||||
}
|
||||
return std::string("Infinite lives ") + (cheat == State::ENABLED ? "ON" : "OFF");
|
||||
const std::string_view MODE = (args.size() > 2) ? std::string_view(args[2]) : std::string_view();
|
||||
const std::string RES = applyCheatToggle(Options::cheats.infinite_lives, MODE, "Infinite lives");
|
||||
return RES.empty() ? "usage: cheat infinite lives [on|off]" : RES;
|
||||
}
|
||||
|
||||
// CHEAT INVINCIBILITY [ON|OFF]
|
||||
if (args[0] == "INVINCIBILITY" || args[0] == "INVENCIBILITY") {
|
||||
auto& cheat = Options::cheats.invincible;
|
||||
using State = Options::Cheat::State;
|
||||
if (args.size() == 1) {
|
||||
cheat = (cheat == State::ENABLED) ? State::DISABLED : State::ENABLED;
|
||||
} else if (args[1] == "ON") {
|
||||
if (cheat == State::ENABLED) { return "Invincibility already ON"; }
|
||||
cheat = State::ENABLED;
|
||||
} else if (args[1] == "OFF") {
|
||||
if (cheat == State::DISABLED) { return "Invincibility already OFF"; }
|
||||
cheat = State::DISABLED;
|
||||
} else {
|
||||
return "usage: cheat invincibility [on|off]";
|
||||
}
|
||||
return std::string("Invincibility ") + (cheat == State::ENABLED ? "ON" : "OFF");
|
||||
const std::string_view MODE = (args.size() > 1) ? std::string_view(args[1]) : std::string_view();
|
||||
const std::string RES = applyCheatToggle(Options::cheats.invincible, MODE, "Invincibility");
|
||||
return RES.empty() ? "usage: cheat invincibility [on|off]" : RES;
|
||||
}
|
||||
|
||||
return "usage: cheat [infinite lives|invincibility]";
|
||||
@@ -910,7 +901,7 @@ static auto cmdSize(const std::vector<std::string>& /*unused*/) -> std::string {
|
||||
}
|
||||
|
||||
// CONSOLE [TRANSPARENT [ON|OFF]|BG|MSG|PROMPT|COMMAND <0-255>]
|
||||
static auto cmdConsole(const std::vector<std::string>& args) -> std::string { // NOLINT(readability-function-cognitive-complexity)
|
||||
static auto cmdConsole(const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty()) {
|
||||
return std::string("Console ") + (Options::console.transparent ? "transparent" : "solid") + " bg:" + std::to_string(Options::console.bg_color) + " msg:" + std::to_string(Options::console.msg_color) + " prompt:" + std::to_string(Options::console.prompt_color) + " cmd:" + std::to_string(Options::console.command_color);
|
||||
}
|
||||
@@ -955,7 +946,7 @@ static auto cmdConsole(const std::vector<std::string>& args) -> std::string { /
|
||||
|
||||
// ── CommandRegistry ──────────────────────────────────────────────────────────
|
||||
|
||||
void CommandRegistry::registerHandlers() { // NOLINT(readability-function-cognitive-complexity)
|
||||
void CommandRegistry::registerHandlers() {
|
||||
handlers_["cmd_shader"] = cmdShader;
|
||||
handlers_["cmd_border"] = cmdBorder;
|
||||
handlers_["cmd_fullscreen"] = cmdFullscreen;
|
||||
@@ -1074,7 +1065,108 @@ void CommandRegistry::registerHandlers() { // NOLINT(readability-function-cogni
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandRegistry::load(const std::string& yaml_path) { // NOLINT(readability-function-cognitive-complexity)
|
||||
namespace {
|
||||
// Parseja un node "scope" (string o sequence) a un vector. Buit si node és buit.
|
||||
auto parseScopeNode(const fkyaml::node& scope_node) -> std::vector<std::string> {
|
||||
std::vector<std::string> result;
|
||||
if (scope_node.is_sequence()) {
|
||||
std::ranges::transform(scope_node, std::back_inserter(result), [](const auto& s) { return s.template get_value<std::string>(); });
|
||||
} else {
|
||||
result.push_back(scope_node.get_value<std::string>());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parseja un mapping de path → [options] en l'unordered_map de destí
|
||||
void parseCompletionsNode(const fkyaml::node& completions_node, std::unordered_map<std::string, std::vector<std::string>>& out) {
|
||||
for (auto it = completions_node.begin(); it != completions_node.end(); ++it) {
|
||||
auto path = it.key().get_value<std::string>();
|
||||
std::vector<std::string> opts;
|
||||
std::ranges::transform(*it, std::back_inserter(opts), [](const auto& opt) { return opt.template get_value<std::string>(); });
|
||||
out[path] = std::move(opts);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Aplica camps "debug_extras" sobre un CommandDef ja inicialitzat (només en _DEBUG)
|
||||
void applyDebugExtras(const fkyaml::node& extras, CommandDef& def) {
|
||||
if (extras.contains("description")) { def.description = extras["description"].get_value<std::string>(); }
|
||||
if (extras.contains("usage")) { def.usage = extras["usage"].get_value<std::string>(); }
|
||||
if (extras.contains("hidden")) { def.hidden = extras["hidden"].get_value<bool>(); }
|
||||
if (extras.contains("help_hidden")) { def.help_hidden = extras["help_hidden"].get_value<bool>(); }
|
||||
if (extras.contains("completions")) {
|
||||
def.completions.clear();
|
||||
parseCompletionsNode(extras["completions"], def.completions);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Parseja un cmd_node a CommandDef. Hereta de la categoria si el comand no defineix scope/debug_only.
|
||||
auto parseCommandDef(const fkyaml::node& cmd_node, const std::string& category, bool cat_debug_only, const std::vector<std::string>& cat_scopes) -> CommandDef {
|
||||
CommandDef def;
|
||||
def.keyword = cmd_node["keyword"].get_value<std::string>();
|
||||
def.handler_id = cmd_node["handler"].get_value<std::string>();
|
||||
def.category = category;
|
||||
def.description = cmd_node.contains("description") ? cmd_node["description"].get_value<std::string>() : "";
|
||||
def.usage = cmd_node.contains("usage") ? cmd_node["usage"].get_value<std::string>() : def.keyword;
|
||||
def.instant = cmd_node.contains("instant") && cmd_node["instant"].get_value<bool>();
|
||||
def.hidden = cmd_node.contains("hidden") && cmd_node["hidden"].get_value<bool>();
|
||||
def.debug_only = cat_debug_only || (cmd_node.contains("debug_only") && cmd_node["debug_only"].get_value<bool>());
|
||||
def.help_hidden = cmd_node.contains("help_hidden") && cmd_node["help_hidden"].get_value<bool>();
|
||||
def.dynamic_completions = cmd_node.contains("dynamic_completions") && cmd_node["dynamic_completions"].get_value<bool>();
|
||||
|
||||
if (cmd_node.contains("scope")) {
|
||||
def.scopes = parseScopeNode(cmd_node["scope"]);
|
||||
} else if (!cat_scopes.empty()) {
|
||||
def.scopes = cat_scopes;
|
||||
} else {
|
||||
def.scopes.emplace_back("global");
|
||||
}
|
||||
|
||||
if (cmd_node.contains("completions")) {
|
||||
parseCompletionsNode(cmd_node["completions"], def.completions);
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
if (cmd_node.contains("debug_extras")) {
|
||||
applyDebugExtras(cmd_node["debug_extras"], def);
|
||||
}
|
||||
#endif
|
||||
return def;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Implementació del handler "cmd_help"
|
||||
auto CommandRegistry::buildHelp(const std::vector<std::string>& args) const -> std::string {
|
||||
if (!args.empty()) {
|
||||
// HELP KEYS [scope]: referencia de atajos de teclado
|
||||
if (args[0] == "KEYS") {
|
||||
return generateKeysHelp(args.size() > 1 ? args[1] : "");
|
||||
}
|
||||
// HELP <command>: mostrar ayuda detallada de un comando
|
||||
const auto* cmd = findCommand(args[0]);
|
||||
if (cmd == nullptr) { return "Unknown command: " + args[0]; }
|
||||
std::string kw_lower = cmd->keyword;
|
||||
std::ranges::transform(kw_lower, kw_lower.begin(), ::tolower);
|
||||
std::string result = kw_lower + ": " + cmd->description + "\n" + cmd->usage;
|
||||
|
||||
// Listar subcomandos/opciones si hay completions
|
||||
const auto OPTS = getCompletions(cmd->keyword);
|
||||
if (!OPTS.empty()) {
|
||||
result += "\noptions:";
|
||||
for (const auto& opt : OPTS) {
|
||||
std::string opt_lower = opt;
|
||||
std::ranges::transform(opt_lower, opt_lower.begin(), ::tolower);
|
||||
result += " " + opt_lower;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
std::cout << generateTerminalHelp();
|
||||
return generateConsoleHelp();
|
||||
}
|
||||
|
||||
void CommandRegistry::load(const std::string& yaml_path) {
|
||||
registerHandlers();
|
||||
|
||||
// Cargar y parsear el YAML
|
||||
@@ -1098,121 +1190,21 @@ void CommandRegistry::load(const std::string& yaml_path) { // NOLINT(readabilit
|
||||
for (const auto& cat_node : yaml["categories"]) {
|
||||
const auto CATEGORY = cat_node["name"].get_value<std::string>();
|
||||
const bool CAT_DEBUG_ONLY = cat_node.contains("debug_only") && cat_node["debug_only"].get_value<bool>();
|
||||
|
||||
// Scopes por defecto de la categoría
|
||||
std::vector<std::string> cat_scopes;
|
||||
if (cat_node.contains("scope")) {
|
||||
const auto& scope_node = cat_node["scope"];
|
||||
if (scope_node.is_sequence()) {
|
||||
std::ranges::transform(scope_node, std::back_inserter(cat_scopes), [](const auto& s) { return s.template get_value<std::string>(); });
|
||||
} else {
|
||||
cat_scopes.push_back(scope_node.get_value<std::string>());
|
||||
}
|
||||
}
|
||||
const std::vector<std::string> CAT_SCOPES = cat_node.contains("scope") ? parseScopeNode(cat_node["scope"]) : std::vector<std::string>{};
|
||||
|
||||
if (!cat_node.contains("commands")) { continue; }
|
||||
|
||||
for (const auto& cmd_node : cat_node["commands"]) {
|
||||
CommandDef def;
|
||||
def.keyword = cmd_node["keyword"].get_value<std::string>();
|
||||
def.handler_id = cmd_node["handler"].get_value<std::string>();
|
||||
def.category = CATEGORY;
|
||||
def.description = cmd_node.contains("description") ? cmd_node["description"].get_value<std::string>() : "";
|
||||
def.usage = cmd_node.contains("usage") ? cmd_node["usage"].get_value<std::string>() : def.keyword;
|
||||
def.instant = cmd_node.contains("instant") && cmd_node["instant"].get_value<bool>();
|
||||
def.hidden = cmd_node.contains("hidden") && cmd_node["hidden"].get_value<bool>();
|
||||
def.debug_only = CAT_DEBUG_ONLY || (cmd_node.contains("debug_only") && cmd_node["debug_only"].get_value<bool>());
|
||||
def.help_hidden = cmd_node.contains("help_hidden") && cmd_node["help_hidden"].get_value<bool>();
|
||||
def.dynamic_completions = cmd_node.contains("dynamic_completions") && cmd_node["dynamic_completions"].get_value<bool>();
|
||||
|
||||
// Scopes: del comando, o hereda de la categoría, o "global" por defecto
|
||||
if (cmd_node.contains("scope")) {
|
||||
const auto& scope_node = cmd_node["scope"];
|
||||
if (scope_node.is_sequence()) {
|
||||
std::ranges::transform(scope_node, std::back_inserter(def.scopes), [](const auto& s) { return s.template get_value<std::string>(); });
|
||||
} else {
|
||||
def.scopes.push_back(scope_node.get_value<std::string>());
|
||||
}
|
||||
} else if (!cat_scopes.empty()) {
|
||||
def.scopes = cat_scopes;
|
||||
} else {
|
||||
def.scopes.emplace_back("global");
|
||||
}
|
||||
|
||||
// Completions estáticas
|
||||
if (cmd_node.contains("completions")) {
|
||||
auto completions_node = cmd_node["completions"];
|
||||
for (auto it = completions_node.begin(); it != completions_node.end(); ++it) {
|
||||
auto path = it.key().get_value<std::string>();
|
||||
std::vector<std::string> opts;
|
||||
const auto& options_node = *it;
|
||||
std::ranges::transform(options_node, std::back_inserter(opts), [](const auto& opt) { return opt.template get_value<std::string>(); });
|
||||
def.completions[path] = std::move(opts);
|
||||
}
|
||||
}
|
||||
|
||||
// Aplicar debug_extras en debug builds
|
||||
#ifdef _DEBUG
|
||||
if (cmd_node.contains("debug_extras")) {
|
||||
const auto& extras = cmd_node["debug_extras"];
|
||||
if (extras.contains("description")) { def.description = extras["description"].get_value<std::string>(); }
|
||||
if (extras.contains("usage")) { def.usage = extras["usage"].get_value<std::string>(); }
|
||||
if (extras.contains("hidden")) { def.hidden = extras["hidden"].get_value<bool>(); }
|
||||
if (extras.contains("help_hidden")) { def.help_hidden = extras["help_hidden"].get_value<bool>(); }
|
||||
if (extras.contains("completions")) {
|
||||
def.completions.clear();
|
||||
auto extras_completions = extras["completions"];
|
||||
for (auto it = extras_completions.begin(); it != extras_completions.end(); ++it) {
|
||||
auto path = it.key().get_value<std::string>();
|
||||
std::vector<std::string> opts;
|
||||
const auto& options_node = *it;
|
||||
std::ranges::transform(options_node, std::back_inserter(opts), [](const auto& opt) { return opt.template get_value<std::string>(); });
|
||||
def.completions[path] = std::move(opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// En Release: saltar comandos debug_only
|
||||
CommandDef def = parseCommandDef(cmd_node, CATEGORY, CAT_DEBUG_ONLY, CAT_SCOPES);
|
||||
#ifndef _DEBUG
|
||||
if (def.debug_only) { continue; }
|
||||
#endif
|
||||
|
||||
commands_.push_back(std::move(def));
|
||||
}
|
||||
}
|
||||
|
||||
// Registrar el handler de HELP (captura this)
|
||||
handlers_["cmd_help"] = [this](const std::vector<std::string>& args) -> std::string {
|
||||
if (!args.empty()) {
|
||||
// HELP KEYS [scope]: referencia de atajos de teclado
|
||||
if (args[0] == "KEYS") {
|
||||
return generateKeysHelp(args.size() > 1 ? args[1] : "");
|
||||
}
|
||||
// HELP <command>: mostrar ayuda detallada de un comando
|
||||
const auto* cmd = findCommand(args[0]);
|
||||
if (cmd != nullptr) {
|
||||
std::string kw_lower = cmd->keyword;
|
||||
std::ranges::transform(kw_lower, kw_lower.begin(), ::tolower);
|
||||
std::string result = kw_lower + ": " + cmd->description + "\n" + cmd->usage;
|
||||
|
||||
// Listar subcomandos/opciones si hay completions
|
||||
auto opts = getCompletions(cmd->keyword);
|
||||
if (!opts.empty()) {
|
||||
result += "\noptions:";
|
||||
for (const auto& opt : opts) {
|
||||
std::string opt_lower = opt;
|
||||
std::ranges::transform(opt_lower, opt_lower.begin(), ::tolower);
|
||||
result += " " + opt_lower;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return "Unknown command: " + args[0];
|
||||
}
|
||||
std::cout << generateTerminalHelp();
|
||||
return generateConsoleHelp();
|
||||
};
|
||||
// Registrar el handler de HELP (delega a buildHelp per mantenir baixa la complexitat de load)
|
||||
handlers_["cmd_help"] = [this](const std::vector<std::string>& args) -> std::string { return buildHelp(args); };
|
||||
|
||||
// Aplanar completions en el mapa global
|
||||
for (const auto& cmd : commands_) {
|
||||
@@ -1300,12 +1292,17 @@ auto CommandRegistry::generateTerminalHelp() const -> std::string {
|
||||
return out.str();
|
||||
}
|
||||
|
||||
auto CommandRegistry::generateConsoleHelp() const -> std::string { // NOLINT(readability-function-cognitive-complexity)
|
||||
auto CommandRegistry::generateConsoleHelp() const -> std::string {
|
||||
// Agrupar comandos visibles por scope
|
||||
std::string global_cmds;
|
||||
std::string debug_cmds;
|
||||
std::string editor_cmds;
|
||||
|
||||
auto append_csv = [](std::string& dst, const std::string& token) {
|
||||
if (!dst.empty()) { dst += ", "; }
|
||||
dst += token;
|
||||
};
|
||||
|
||||
for (const auto& cmd : commands_) {
|
||||
if (cmd.help_hidden) { continue; }
|
||||
if (!isCommandVisible(cmd)) { continue; }
|
||||
@@ -1315,16 +1312,12 @@ auto CommandRegistry::generateConsoleHelp() const -> std::string { // NOLINT(re
|
||||
|
||||
// Clasificar por el PRIMER scope del comando
|
||||
const std::string& primary = cmd.scopes.empty() ? "global" : cmd.scopes[0];
|
||||
|
||||
if (primary == "editor") {
|
||||
if (!editor_cmds.empty()) { editor_cmds += ", "; }
|
||||
editor_cmds += kw_lower;
|
||||
append_csv(editor_cmds, kw_lower);
|
||||
} else if (primary == "debug") {
|
||||
if (!debug_cmds.empty()) { debug_cmds += ", "; }
|
||||
debug_cmds += kw_lower;
|
||||
append_csv(debug_cmds, kw_lower);
|
||||
} else {
|
||||
if (!global_cmds.empty()) { global_cmds += ", "; }
|
||||
global_cmds += kw_lower;
|
||||
append_csv(global_cmds, kw_lower);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,4 +59,5 @@ class CommandRegistry {
|
||||
void registerHandlers();
|
||||
[[nodiscard]] auto isCommandVisible(const CommandDef& cmd) const -> bool;
|
||||
[[nodiscard]] static auto generateKeysHelp(const std::string& scope_filter) -> std::string;
|
||||
[[nodiscard]] auto buildHelp(const std::vector<std::string>& args) const -> std::string; // Implementació del handler "cmd_help"
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user