251 lines
7.7 KiB
C++
251 lines
7.7 KiB
C++
#include "command2.h"
|
|
#include "escape.h"
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
|
|
using namespace std;
|
|
using namespace ts;
|
|
|
|
#define CONVERTER_ST(type, m_decode, m_encode) \
|
|
CONVERTER_METHOD_DECODE(type, impl::converter_ ##type ##_decode) { \
|
|
m_decode; \
|
|
} \
|
|
\
|
|
CONVERTER_METHOD_ENCODE(type, impl::converter_ ##type ##_encode) { \
|
|
m_encode \
|
|
}
|
|
|
|
#define CONVERTER_PRIMITIVE_ST(type, m_decode) CONVERTER_ST(type, \
|
|
return m_decode;, \
|
|
return std::to_string(std::any_cast<type>(value)); \
|
|
)
|
|
|
|
CONVERTER_PRIMITIVE_ST(int8_t, std::stol(str) & 0xFF);
|
|
CONVERTER_PRIMITIVE_ST(uint8_t, std::stoul(str) & 0xFF);
|
|
|
|
CONVERTER_PRIMITIVE_ST(int16_t, std::stol(str) & 0xFFFF);
|
|
CONVERTER_PRIMITIVE_ST(uint16_t, std::stoul(str) & 0xFFFF);
|
|
|
|
CONVERTER_PRIMITIVE_ST(int32_t, std::stol(str) & 0xFFFFFFFF);
|
|
CONVERTER_PRIMITIVE_ST(uint32_t, std::stoul(str) & 0xFFFFFFFF);
|
|
|
|
CONVERTER_PRIMITIVE_ST(int64_t, std::stoll(str));
|
|
CONVERTER_PRIMITIVE_ST(uint64_t, std::stoull(str))
|
|
|
|
CONVERTER_PRIMITIVE_ST(bool, str == "1" || str == "true");
|
|
CONVERTER_PRIMITIVE_ST(float, std::stof(str));
|
|
CONVERTER_PRIMITIVE_ST(double, std::stod(str));
|
|
CONVERTER_PRIMITIVE_ST(long_double, std::stold(str));
|
|
|
|
CONVERTER_ST(std__string, return str;, return std::any_cast<std__string>(value););
|
|
CONVERTER_ST(const_char__ , return str.c_str();, return std::string(std::any_cast<const_char__>(value)););
|
|
|
|
command::command(const std::string& command, bool editable) {
|
|
this->handle = make_shared<impl::command_data>();
|
|
this->handle->editiable = editable;
|
|
this->handle->command = command;
|
|
}
|
|
|
|
std::string command::identifier() const {
|
|
return this->handle->command;
|
|
}
|
|
|
|
void command::set_identifier(const std::string &command) {
|
|
this->handle->command = command;
|
|
}
|
|
|
|
command_bulk command::bulk(size_t index) {
|
|
if(this->handle->bulks.size() <= index) {
|
|
if(!this->handle->editiable)
|
|
throw command_bulk_exceed_index_exception();
|
|
|
|
while(this->handle->bulks.size() <= index) {
|
|
auto bulk = make_shared<impl::command_bulk>();
|
|
bulk->handle = this->handle.get();
|
|
this->handle->bulks.push_back(std::move(bulk));
|
|
}
|
|
}
|
|
|
|
return {index, this->handle->bulks[index]};
|
|
}
|
|
|
|
const command_bulk command::bulk(size_t index) const {
|
|
if(this->handle->bulks.size() <= index)
|
|
throw command_bulk_exceed_index_exception();
|
|
|
|
return {index, this->handle->bulks[index]};
|
|
}
|
|
|
|
size_t command::bulk_count() const {
|
|
return this->handle->bulks.size();
|
|
}
|
|
|
|
|
|
command_entry command::value(const std::string &key) {
|
|
return this->bulk(0)[key];
|
|
}
|
|
|
|
const command_entry command::value(const std::string &key) const {
|
|
return this->bulk(0)[key];
|
|
}
|
|
|
|
bool command::has_value(const std::string &key) const {
|
|
if(this->bulk_count() == 0) return false;
|
|
|
|
return this->bulk(0).has(key);
|
|
}
|
|
|
|
bool command::has_trigger(const std::string &key) const {
|
|
for(const auto& trigger : this->handle->triggers)
|
|
if(trigger == key)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void command::set_trigger(const std::string &key, bool value) {
|
|
auto& triggers = this->handle->triggers;
|
|
auto it = find(triggers.begin(), triggers.end(), key);
|
|
if(it == triggers.end() && value) {
|
|
triggers.push_back(key);
|
|
} else if(it != triggers.end() && !value) {
|
|
triggers.erase(it);
|
|
}
|
|
}
|
|
|
|
command command::parse(const std::string_view &data, bool expect_type, bool drop_non_utf8) {
|
|
command result;
|
|
|
|
size_t current_index = std::string::npos, end_index;
|
|
if(expect_type) {
|
|
current_index = data.find(' ', 0);
|
|
if(current_index == std::string::npos){
|
|
result.set_identifier(std::string(data));
|
|
return result;
|
|
} else {
|
|
result.set_identifier(std::string(data.substr(0, current_index)));
|
|
}
|
|
}
|
|
|
|
size_t bulk_index = 0;
|
|
while(++current_index > 0 || (current_index == 0 && !expect_type && (expect_type = true))) {
|
|
end_index = data.find_first_of(" |", current_index);
|
|
|
|
if(end_index != current_index) { /* else we've found another space or a pipe */
|
|
if(data[current_index] == '-') {
|
|
string trigger(data.substr(current_index + 1, end_index - current_index - 1));
|
|
result.set_trigger(trigger);
|
|
} else {
|
|
auto index_assign = data.find_first_of('=', current_index);
|
|
string key, value;
|
|
if(index_assign == string::npos || index_assign > end_index) {
|
|
key = data.substr(current_index, end_index - current_index);
|
|
} else {
|
|
key = data.substr(current_index, index_assign - current_index);
|
|
try {
|
|
value = query::unescape(string(data.substr(index_assign + 1, end_index - index_assign - 1)), true);
|
|
} catch(std::invalid_argument& ex) {
|
|
/* invalid character at index X */
|
|
if(!drop_non_utf8)
|
|
throw;
|
|
goto skip_assign;
|
|
}
|
|
}
|
|
|
|
{
|
|
const static auto key_validator = [](char c){ return !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-'); };
|
|
auto invalid_index = find_if(key.begin(), key.end(), key_validator);
|
|
if(invalid_index != key.end())
|
|
throw command_malformed_exception(current_index + distance(key.begin(), invalid_index));
|
|
|
|
if(!key.empty() && !result[bulk_index].has(key))
|
|
result[bulk_index][key] = value;
|
|
}
|
|
|
|
skip_assign:;
|
|
}
|
|
}
|
|
|
|
if(end_index < data.length() && data[end_index] == '|')
|
|
bulk_index++;
|
|
current_index = end_index;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string command::build(ts::command::format::value type) {
|
|
if(type == format::QUERY || type == format::BRACE_ESCAPED_QUERY) {
|
|
std::stringstream ss;
|
|
|
|
if(!this->handle->command.empty())
|
|
ss << this->handle->command << " ";
|
|
|
|
if(!this->handle->bulks.empty()) {
|
|
auto max_bulk_index = this->handle->bulks.size() - 1;
|
|
for(size_t bulk_index = 0; bulk_index <= max_bulk_index; bulk_index++) {
|
|
auto& bulk = this->handle->bulks[bulk_index];
|
|
if(bulk->values.empty()) continue; //Do not remove me!
|
|
|
|
auto max_pair_index = bulk->values.size() - 1;
|
|
auto pair_it = bulk->values.begin();
|
|
|
|
for(size_t pair_index = 0; pair_index <= max_pair_index; pair_index++) {
|
|
auto pair = *(pair_it++);
|
|
|
|
auto value = pair.second->casted ? pair.second->to_string(pair.second->value) : pair.second->value.has_value() ? any_cast<std::string>(pair.second->value) : "";
|
|
ss << pair.first << "=";
|
|
if(type == format::BRACE_ESCAPED_QUERY) {
|
|
ss << "\"" << value << "\"";
|
|
} else ss << query::escape(value);
|
|
|
|
if(pair_index != max_pair_index)
|
|
ss << " ";
|
|
}
|
|
|
|
if(bulk_index != max_bulk_index)
|
|
ss << " | ";
|
|
}
|
|
}
|
|
|
|
if(!this->handle->triggers.empty()) {
|
|
auto max_trigger_index = this->handle->triggers.size() - 1;
|
|
for(size_t trigger_index = 0; trigger_index <= max_trigger_index; trigger_index++) {
|
|
ss << " -" << this->handle->triggers[trigger_index];
|
|
}
|
|
}
|
|
|
|
return ss.str();
|
|
}
|
|
return "";
|
|
}
|
|
|
|
bool command_bulk::has(const std::string &key) const {
|
|
auto& values = this->handle->values;
|
|
auto index = values.find(key);
|
|
return index != values.end();
|
|
}
|
|
|
|
command_entry command_bulk::value(const std::string &key) {
|
|
auto& values = this->handle->values;
|
|
auto index = values.find(key);
|
|
if(index == values.end()) {
|
|
if(!this->handle->handle->editiable)
|
|
throw command_value_missing_exception(this->bulk_index, key);
|
|
|
|
auto result = make_shared<impl::command_value>();
|
|
values[key] = result;
|
|
return command_entry(result);
|
|
}
|
|
return command_entry(index->second);
|
|
}
|
|
|
|
const command_entry command_bulk::value(const std::string &key) const {
|
|
auto& values = this->handle->values;
|
|
auto index = values.find(key);
|
|
if(index == values.end())
|
|
throw command_value_missing_exception(this->bulk_index, key);
|
|
|
|
return command_entry(index->second);
|
|
}
|
|
|
|
command_entry command_entry::empty{}; |