TeaSpeakLibrary/src/query/command2.cpp

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{};