338 lines
15 KiB
C++
338 lines
15 KiB
C++
#pragma once
|
|
|
|
#include <string>
|
|
#include <cstddef>
|
|
#include <vector>
|
|
#include <optional>
|
|
#include <string_view>
|
|
|
|
#include "escape.h"
|
|
#include "converters/converter.h"
|
|
#include "command_exception.h"
|
|
|
|
namespace ts {
|
|
namespace impl {
|
|
inline bool value_raw_impl(const std::string_view& data, const std::string_view& key, size_t& begin, size_t* end) {
|
|
size_t index{0}, findex, max{data.size()}, key_size{key.size()};
|
|
do {
|
|
findex = data.find(key, index);
|
|
if(findex > max) return false;
|
|
|
|
/* not starting with a space so not a match */
|
|
if(findex != 0 && data[findex - 1] != ' ') {
|
|
index = findex + key_size;
|
|
continue;
|
|
}
|
|
|
|
findex += key_size;
|
|
if(findex < max) {
|
|
if(data[findex] == '=')
|
|
begin = findex + 1;
|
|
else if(data[findex] == ' ')
|
|
begin = findex; /* empty value */
|
|
else {
|
|
index = findex + key_size;
|
|
continue;
|
|
}
|
|
|
|
if(end) *end = data.find(' ', findex);
|
|
return true;
|
|
} else {
|
|
begin = max;
|
|
if(end) *end = max;
|
|
return true;
|
|
}
|
|
} while(true);
|
|
}
|
|
|
|
class command_string_parser {
|
|
public:
|
|
[[nodiscard]] inline bool is_empty() const noexcept { return this->data.empty(); }
|
|
|
|
[[nodiscard]] inline bool has_key(const std::string_view& key) const {
|
|
size_t begin{0};
|
|
return value_raw_impl(this->data, key, begin, nullptr);
|
|
}
|
|
|
|
[[nodiscard]] inline std::string_view value_raw(const std::string_view& key) const {
|
|
bool tmp;
|
|
auto result = this->value_raw(key, tmp);
|
|
if(!tmp) throw command_value_missing_exception{index, std::string{key}};
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] inline std::string_view value_raw(const std::string_view& key, bool& has_been_found) const noexcept {
|
|
size_t begin{0}, end;
|
|
has_been_found = value_raw_impl(this->data, key, begin, &end);
|
|
if(!has_been_found) return {};
|
|
|
|
return this->data.substr(begin, end - begin);
|
|
}
|
|
|
|
[[nodiscard]] inline std::string value(const std::string_view& key) const {
|
|
const auto value = this->value_raw(key);
|
|
if(value.empty()) return std::string{};
|
|
|
|
return query::unescape(std::string{value}, false);
|
|
}
|
|
|
|
[[nodiscard]] inline std::string value(const std::string_view& key, bool& has_been_found) const noexcept {
|
|
const auto value = this->value_raw(key, has_been_found);
|
|
if(value.empty()) return std::string{};
|
|
|
|
return query::unescape(std::string{value}, false);
|
|
}
|
|
|
|
template <typename T>
|
|
[[nodiscard]] inline T value_as(const std::string_view& key) const {
|
|
static_assert(converter<T>::supported, "Target type isn't supported!");
|
|
static_assert(!converter<T>::supported || converter<T>::from_string_view, "Target type dosn't support parsing");
|
|
|
|
return converter<T>::from_string_view(this->value(key));
|
|
}
|
|
|
|
[[nodiscard]] inline size_t command_character_index() const { return this->abs_index; }
|
|
[[nodiscard]] inline size_t key_command_character_index(const std::string_view& key) const {
|
|
size_t begin{0};
|
|
if(!value_raw_impl(this->data, key, begin, nullptr)) return this->abs_index;
|
|
return this->abs_index + begin;
|
|
}
|
|
protected:
|
|
command_string_parser(size_t index, size_t abs_index, std::string_view data) : index{index}, abs_index{abs_index}, data{data} {}
|
|
|
|
size_t abs_index{};
|
|
size_t index{};
|
|
std::string_view data{};
|
|
};
|
|
}
|
|
|
|
struct command_bulk : public impl::command_string_parser {
|
|
command_bulk(size_t index, size_t abs_index, std::string_view data) : command_string_parser{index, abs_index, data} {}
|
|
|
|
inline bool next_entry(size_t& index, std::string_view& key, std::string& value) const {
|
|
auto next_key = this->data.find_first_not_of(' ', index);
|
|
if(next_key == std::string::npos) return false;
|
|
|
|
auto key_end = this->data.find_first_of(" =", next_key);
|
|
if(key_end == std::string::npos || this->data[key_end] == ' ') {
|
|
key = this->data.substr(next_key, key_end - next_key);
|
|
value.clear();
|
|
index = key_end;
|
|
} else {
|
|
key = this->data.substr(next_key, key_end - next_key);
|
|
auto value_end = this->data.find_first_of(' ', key_end + 1);
|
|
|
|
if(value_end == std::string::npos) {
|
|
value = query::unescape(value = this->data.substr(key_end + 1), false);
|
|
index = value_end;
|
|
} else {
|
|
value = query::unescape(value = this->data.substr(key_end + 1, value_end - key_end - 1), false);
|
|
index = value_end + 1;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class command_parser : public impl::command_string_parser {
|
|
public:
|
|
enum struct parse_status {
|
|
succeeded,
|
|
failed
|
|
};
|
|
|
|
explicit command_parser(std::string command) : impl::command_string_parser{std::string::npos, 0, command}, _command{std::move(command)} { }
|
|
|
|
bool parse(bool /* contains identifier */);
|
|
|
|
[[nodiscard]] inline const std::string_view& identifier() const noexcept { return this->command_type; }
|
|
[[nodiscard]] inline size_t bulk_count() const noexcept { return this->_bulks.size(); }
|
|
[[nodiscard]] inline const command_bulk& bulk(size_t index) const noexcept {
|
|
if(this->_bulks.size() <= index) return empty_bulk;
|
|
return this->_bulks[index];
|
|
}
|
|
|
|
const command_bulk& operator[](size_t index) const { return this->bulk(index); }
|
|
|
|
[[nodiscard]] inline bool has_switch(const std::string_view& key) const noexcept {
|
|
size_t index{0};
|
|
do {
|
|
index = this->data.find(key, index);
|
|
index -= 1;
|
|
if(index > key.size()) return false;
|
|
if(this->data[index] == '-') return index == 0 || this->data[index - 1] == ' ';
|
|
index += 2;
|
|
} while(true);
|
|
}
|
|
|
|
[[nodiscard]] const std::vector<command_bulk>& bulks() const { return this->_bulks; }
|
|
|
|
[[nodiscard]] std::optional<size_t> next_bulk_containing(const std::string_view& /* key */, size_t /* bulk offset */) const;
|
|
private:
|
|
static command_bulk empty_bulk;
|
|
|
|
const std::string _command{};
|
|
std::string_view command_type{};
|
|
std::vector<command_bulk> _bulks{};
|
|
};
|
|
|
|
class command_builder_bulk {
|
|
template <typename vector_t>
|
|
friend class command_builder_impl;
|
|
public:
|
|
inline void reserve(size_t length, bool accumulative = true) {
|
|
this->bulk.reserve(length + (accumulative ? this->bulk.size() : 0));
|
|
}
|
|
|
|
inline void put(const std::string_view& key, const std::string_view& value) {
|
|
size_t begin, end;
|
|
if(impl::value_raw_impl(this->bulk, key, begin, &end)) {
|
|
std::string result{};
|
|
result.reserve(this->bulk.size());
|
|
|
|
result.append(this->bulk, 0, begin - key.size() - 1); /* key incl = */
|
|
result.append(this->bulk, end + 1); /* get rid of the space */
|
|
this->bulk = result;
|
|
}
|
|
this->impl_put_unchecked(key, value);
|
|
}
|
|
|
|
template <typename T, std::enable_if_t<!(std::is_same<T, std::string_view>::value || std::is_same<T, std::string>::value), int> = 1>
|
|
inline void put(const std::string_view& key, const T& value) {
|
|
static_assert(converter<T>::supported, "Target type isn't supported!");
|
|
static_assert(!converter<T>::supported || converter<T>::to_string, "Target type dosn't support building");
|
|
auto data = converter<T>::to_string(value);
|
|
this->put(key, std::string_view{data});
|
|
}
|
|
|
|
#ifdef PROPERTIES_DEFINED
|
|
template <typename PropertyType, typename T, std::enable_if_t<std::is_enum<PropertyType>::value, int> = 0>
|
|
inline void put(PropertyType key, const T& value) {
|
|
this->put(property::name(key), value);
|
|
}
|
|
#endif
|
|
|
|
/* directly puts data without checking for duplicates */
|
|
inline void put_unchecked(const std::string_view& key, const std::string_view& value) {
|
|
this->impl_put_unchecked(key, value);
|
|
}
|
|
|
|
inline void put_unchecked(const std::string_view& key, const std::string& value) {
|
|
this->put_unchecked(key, std::string_view{value});
|
|
}
|
|
|
|
#ifdef PROPERTIES_DEFINED
|
|
template <typename PropertyType, typename T, std::enable_if_t<std::is_enum<PropertyType>::value, int> = 0>
|
|
inline void put_unchecked(PropertyType key, const T& value) {
|
|
this->put_unchecked(property::name(key), value);
|
|
}
|
|
#endif
|
|
|
|
template <typename T, std::enable_if_t<!(std::is_same<T, std::string_view>::value || std::is_same<T, std::string>::value), int> = 1>
|
|
inline void put_unchecked(const std::string_view& key, const T& value) {
|
|
static_assert(converter<T>::supported, "Target type isn't supported!");
|
|
static_assert(!converter<T>::supported || converter<T>::to_string, "Target type dosn't support building");
|
|
auto data = converter<T>::to_string(value);
|
|
this->put_unchecked(key, std::string_view{data});
|
|
}
|
|
private:
|
|
bool& flag_changed;
|
|
std::string& bulk;
|
|
explicit command_builder_bulk(bool& change_flag, std::string& bulk) : flag_changed{change_flag}, bulk{bulk} {}
|
|
|
|
void impl_put_unchecked(const std::string_view& key, const std::string_view& value) {
|
|
auto escaped_value = ts::query::escape(std::string{value});
|
|
|
|
this->bulk.reserve(this->bulk.length() + key.size() + escaped_value.size() + 2);
|
|
this->bulk.append(key);
|
|
if(!escaped_value.empty()) {
|
|
this->bulk.append("=");
|
|
this->bulk.append(escaped_value);
|
|
}
|
|
this->bulk.append(" ");
|
|
this->flag_changed = true;
|
|
}
|
|
};
|
|
|
|
template <typename vector_t = std::vector<std::string>>
|
|
class command_builder_impl {
|
|
public:
|
|
explicit command_builder_impl(std::string command, size_t expected_bulk_size = 128, size_t expected_bulks = 1) : _identifier{std::move(command)}, expected_bulk_size{expected_bulk_size} {
|
|
for(size_t index = 0; index < expected_bulks; index++)
|
|
this->bulks.emplace_back(" ").reserve(expected_bulk_size);
|
|
}
|
|
|
|
inline command_builder_impl<std::vector<std::string>> as_normalized() {
|
|
return command_builder_impl<std::vector<std::string>>{this->expected_bulk_size, this->_identifier, this->bulks.begin(), this->bulks.end()};
|
|
}
|
|
|
|
inline std::string build(bool with_empty = false) const {
|
|
if(this->builded.has_value() && !this->flag_changed)
|
|
return this->builded.value();
|
|
|
|
std::string result{};
|
|
size_t required_size{this->_identifier.size()};
|
|
for(auto& entry : this->bulks)
|
|
required_size += entry.size() + 1;
|
|
|
|
result.append(this->_identifier);
|
|
for(auto it = this->bulks.begin(); it != this->bulks.end(); it++) {
|
|
if(it->empty() && !with_empty) continue;
|
|
|
|
result.append(*it, 0, it->length() - 1);
|
|
if(it + 1 != this->bulks.end())
|
|
result.append("|");
|
|
}
|
|
if(!with_empty && result.ends_with('|'))
|
|
this->builded = result.substr(0, result.length() - 1);
|
|
else
|
|
this->builded = result;
|
|
this->flag_changed = false;
|
|
return this->builded.value();
|
|
}
|
|
|
|
inline void reserve_bulks(size_t count) { this->bulks.reserve(count); }
|
|
|
|
[[nodiscard]] inline command_builder_bulk bulk(size_t index) {
|
|
while(this->bulks.size() <= index)
|
|
this->bulks.emplace_back("").reserve(expected_bulk_size);
|
|
|
|
return command_builder_bulk{this->flag_changed, this->bulks[index]};
|
|
}
|
|
|
|
template <typename KeyT, typename ValueT>
|
|
inline void put(size_t index, const KeyT& key, const ValueT& value) {
|
|
this->bulk(index).put(key, value);
|
|
}
|
|
|
|
/* directly puts data without checking for duplicates */
|
|
template <typename KeyT, typename ValueT>
|
|
inline void put_unchecked(size_t index, const KeyT& key, const ValueT& value) {
|
|
this->bulk(index).put_unchecked(key, value);
|
|
}
|
|
|
|
[[nodiscard]] inline size_t current_size() const {
|
|
if(this->bulks.empty()) return 0;
|
|
|
|
size_t result{0};
|
|
for(const auto& entry : this->bulks)
|
|
result += entry.length() + 2;
|
|
return result;
|
|
}
|
|
|
|
inline void reset() {
|
|
for(auto& bulk : this->bulks)
|
|
bulk = " ";
|
|
}
|
|
private:
|
|
command_builder_impl(size_t expected, std::string identifier, typename vector_t::iterator begin, typename vector_t::iterator end) : expected_bulk_size{expected}, _identifier{std::move(identifier)}, bulks{begin, end} {}
|
|
|
|
const size_t expected_bulk_size;
|
|
const std::string _identifier;
|
|
mutable bool flag_changed{false};
|
|
mutable std::optional<std::string> builded{};
|
|
vector_t bulks{};
|
|
};
|
|
|
|
using command_builder = command_builder_impl<>;
|
|
} |