console_commands: load() delega a parsers i buildHelp (cognitive 69→<25)
This commit is contained in:
@@ -1103,7 +1103,78 @@ void CommandRegistry::registerHandlers() { // NOLINT(readability-function-cogni
|
|||||||
#endif
|
#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
|
||||||
|
|
||||||
|
void CommandRegistry::load(const std::string& yaml_path) {
|
||||||
registerHandlers();
|
registerHandlers();
|
||||||
|
|
||||||
// Cargar y parsear el YAML
|
// Cargar y parsear el YAML
|
||||||
@@ -1127,115 +1198,21 @@ void CommandRegistry::load(const std::string& yaml_path) { // NOLINT(readabilit
|
|||||||
for (const auto& cat_node : yaml["categories"]) {
|
for (const auto& cat_node : yaml["categories"]) {
|
||||||
const auto CATEGORY = cat_node["name"].get_value<std::string>();
|
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>();
|
const bool CAT_DEBUG_ONLY = cat_node.contains("debug_only") && cat_node["debug_only"].get_value<bool>();
|
||||||
|
const std::vector<std::string> CAT_SCOPES = cat_node.contains("scope") ? parseScopeNode(cat_node["scope"]) : std::vector<std::string>{};
|
||||||
// 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>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cat_node.contains("commands")) { continue; }
|
if (!cat_node.contains("commands")) { continue; }
|
||||||
|
|
||||||
for (const auto& cmd_node : cat_node["commands"]) {
|
for (const auto& cmd_node : cat_node["commands"]) {
|
||||||
CommandDef def;
|
CommandDef def = parseCommandDef(cmd_node, CATEGORY, CAT_DEBUG_ONLY, CAT_SCOPES);
|
||||||
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()) {
|
|
||||||
for (const auto& s : scope_node) { def.scopes.push_back(s.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;
|
|
||||||
std::ranges::transform(*it, 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;
|
|
||||||
std::ranges::transform(*it, 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
|
|
||||||
#ifndef _DEBUG
|
#ifndef _DEBUG
|
||||||
if (def.debug_only) { continue; }
|
if (def.debug_only) { continue; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
commands_.push_back(std::move(def));
|
commands_.push_back(std::move(def));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registrar el handler de HELP (captura this)
|
// 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 {
|
handlers_["cmd_help"] = [this](const std::vector<std::string>& args) -> std::string { return buildHelp(args); };
|
||||||
if (!args.empty()) {
|
|
||||||
// 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();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Aplanar completions en el mapa global
|
// Aplanar completions en el mapa global
|
||||||
for (const auto& cmd : commands_) {
|
for (const auto& cmd : commands_) {
|
||||||
@@ -1245,6 +1222,30 @@ void CommandRegistry::load(const std::string& yaml_path) { // NOLINT(readabilit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto CommandRegistry::buildHelp(const std::vector<std::string>& args) const -> std::string {
|
||||||
|
if (args.empty()) {
|
||||||
|
std::cout << generateTerminalHelp();
|
||||||
|
return generateConsoleHelp();
|
||||||
|
}
|
||||||
|
// HELP <command>: ajuda detallada
|
||||||
|
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;
|
||||||
|
|
||||||
|
const auto OPTS = getCompletions(cmd->keyword);
|
||||||
|
if (OPTS.empty()) { return result; }
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
auto CommandRegistry::findCommand(const std::string& keyword) const -> const CommandDef* {
|
auto CommandRegistry::findCommand(const std::string& keyword) const -> const CommandDef* {
|
||||||
auto it = std::ranges::find_if(commands_,
|
auto it = std::ranges::find_if(commands_,
|
||||||
[&keyword](const auto& cmd) { return cmd.keyword == keyword; });
|
[&keyword](const auto& cmd) { return cmd.keyword == keyword; });
|
||||||
|
|||||||
@@ -58,4 +58,5 @@ class CommandRegistry {
|
|||||||
|
|
||||||
void registerHandlers();
|
void registerHandlers();
|
||||||
[[nodiscard]] auto isCommandVisible(const CommandDef& cmd) const -> bool;
|
[[nodiscard]] auto isCommandVisible(const CommandDef& cmd) const -> bool;
|
||||||
|
[[nodiscard]] auto buildHelp(const std::vector<std::string>& args) const -> std::string; // Cos del handler HELP (extret per reduir complexitat de load())
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user