ores.cpp.shell-command.operation_implementation
Table of Contents
The body of one operation model's shell command unit.
Each handler fills the declared request from a token vector and sends it on the
request's own nats_subject, so the command and the protocol cannot disagree
about where a message goes. The reply is written as JSON: a declared response
has whatever shape its model gave it, and printing it whole is the only
projection that stays true to every one of them. A response the model gives a
richer reading belongs in a hand-written unit, and the entity archetypes are
where a projection the shell owns is written.
A field the shell cannot fill from a token is a model defect, not a rendering
one: _reject_silent_shell_gap refuses the model rather than dropping the
argument, because a command that silently asks for fewer things than the
protocol declares is worse than one that does not build.
1. Template
The full template source. Edit here and re-tangle with
compass build --direct tangle_codegen_templates to regenerate
library/templates/cpp_shell_operation_implementation.cpp.mustache.
{{! GENERATED FILE — tangled from projects/ores.codegen/library/templates/ores.cpp.shell-command.operation_implementation.org. Edit the org source. }}
{{{cpp_license}}}
{{#operation}}
#include "ores.shell/app/commands/{{component}}/{{entity_singular}}_operations_commands.hpp"
#include "ores.shell/app/command_args.hpp"
#include "ores.shell/app/command_feedback.hpp"
#include "ores.shell/app/command_token.hpp"
#include "ores.shell/app/request_helpers.hpp"
#include "ores.{{component}}.{{subcomponent}}/messaging/{{entity_singular}}_protocol.hpp"
#include "ores.utility/rfl/reflectors.hpp" // IWYU pragma: keep.
#include <cli/cli.h>
#include <cstddef>
#include <functional>
#include <optional>
#include <ostream>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <stdexcept>
#include <string>
#include <vector>
namespace ores::shell::app::commands {
using namespace logging;
using ores::nats::service::nats_client;
{{#shell_has_helpers}}
namespace {
{{/shell_has_helpers}}
{{#shell_has_list}}
/**
* @brief Split a comma-separated token into the elements of a list field.
*/
std::vector<std::string> split_list_token(const std::string& value) {
std::vector<std::string> parts;
std::string current;
for (const char c : value) {
if (c == ',') {
parts.push_back(current);
current.clear();
} else {
current.push_back(c);
}
}
parts.push_back(current);
return parts;
}
{{/shell_has_list}}
{{#shell_has_bool}}
/**
* @brief Read a boolean token, which the shell spells out as a word.
*/
bool parse_flag(const std::string& value, bool& out) {
if (value.empty() || value == "false") {
out = false;
return true;
}
if (value == "true") {
out = true;
return true;
}
return false;
}
{{/shell_has_bool}}
{{#shell_has_helpers}}
} // namespace
{{/shell_has_helpers}}
void {{entity_singular}}_operations_commands::register_commands(cli::Menu& root_menu,
nats_client& session) {
auto menu = std::make_unique<cli::Menu>("{{entity_singular}}");
{{#shell_commands}}
menu->Insert(
"{{command}}",
[&session](std::ostream& out, std::vector<std::string> args) {
process_{{identifier}}(std::ref(out), std::ref(session), std::move(args));
},
"{{usage}}");
{{/shell_commands}}
root_menu.Insert(std::move(menu));
}
{{#shell_commands}}
void {{entity_singular}}_operations_commands::process_{{identifier}}(
std::ostream& out, nats_client& session, const std::vector<std::string>& args) {
BOOST_LOG_SEV(lg(), debug) << "Initiating {{command}} request.";
using request_type = {{namespace}}::{{request}};
// Whether the command presents a token is the protocol's own statement, so
// a message that establishes the session is never asked for one.
if constexpr (request_type::requires_session) {
if (!session.is_logged_in()) {
fail(out) << "You must be logged in to run {{command}}." << std::endl;
return;
}
}
const std::vector<flag_spec> specs{
{{#flags}}
{.name = "{{name}}", .requires_value = true, .default_value = ""},
{{/flags}}
};
const auto parsed = parse_args(args, specs);
if (!parsed) {
fail(out) << parsed.error() << std::endl;
return;
}
constexpr std::size_t positional_count = {{positional_count}};
if (parsed->positionals.size() != positional_count) {
fail(out) << "Expected " << positional_count << " arguments, got "
<< parsed->positionals.size() << "." << std::endl;
return;
}
request_type req;
{{#has_positionals}}
std::size_t next = 0;
{{/has_positionals}}
try {
{{#positionals}}
{{#is_list}}
req.{{name}} = split_list_token(parsed->positionals[next++]);
{{/is_list}}
{{#is_bool}}
if (!parse_flag(parsed->positionals[next++], req.{{name}})) {
fail(out) << "{{name}} must be 'true' or 'false'." << std::endl;
return;
}
{{/is_bool}}
{{#is_string}}
req.{{name}} = parsed->positionals[next++];
{{/is_string}}
{{#needs_from_token}}
req.{{name}} = ores::shell::app::from_token<{{cpp_type}}>(
parsed->positionals[next++], "{{name}}");
{{/needs_from_token}}
{{/positionals}}
{{#flags}}
if (const auto& raw_{{name}} = parsed->flag("{{name}}"); !raw_{{name}}.empty()) {
{{#is_list}}
req.{{name}} = split_list_token(raw_{{name}});
{{/is_list}}
{{#is_bool}}
if (!parse_flag(raw_{{name}}, req.{{name}})) {
fail(out) << "{{name}} must be 'true' or 'false'." << std::endl;
return;
}
{{/is_bool}}
{{#is_string}}
req.{{name}} = raw_{{name}};
{{/is_string}}
{{#needs_from_token}}
req.{{name}} = ores::shell::app::from_token<{{cpp_type}}>(
raw_{{name}}, "{{name}}");
{{/needs_from_token}}
}
{{/flags}}
} catch (const std::exception& e) {
fail(out) << e.what() << std::endl;
return;
}
std::optional<{{namespace}}::{{response_type}}> result;
if constexpr (request_type::requires_session) {
result = do_auth_request<{{namespace}}::{{response_type}}>(
out, session, std::string(req.nats_subject), req);
} else {
result = do_request<{{namespace}}::{{response_type}}>(
out, session, std::string(req.nats_subject), req);
}
if (!result)
return;
out << rfl::json::write(*result) << std::endl;
}
{{/shell_commands}}
}
{{/operation}}
2. See also
- Parent facet: ores.cpp.shell-command.
- Sibling archetype: ores.cpp.shell-command.command_header.
- Sibling archetype: ores.cpp.shell-command.command_tests.
- Template variable reference — the variables this template reads.