255 lines
11 KiB
C++
255 lines
11 KiB
C++
#include "ikvxx/ikvxx.hpp"
|
|
|
|
extern "C" {
|
|
#include "ikv.h"
|
|
}
|
|
|
|
#include <cstdlib>
|
|
#include <limits>
|
|
#include <utility>
|
|
|
|
namespace ikv {
|
|
namespace {
|
|
|
|
ikv_version_t nativeVersion(Version version) {
|
|
return version == Version::v1 ? IKV_VERSION_1 : IKV_VERSION_2;
|
|
}
|
|
|
|
ikv_type_t nativeType(ValueType type) {
|
|
switch (type) {
|
|
case stringValue: return IKV_STRING;
|
|
case intValue: return IKV_INT;
|
|
case realValue: return IKV_FLOAT;
|
|
case booleanValue: return IKV_BOOL;
|
|
case objectValue: return IKV_OBJECT;
|
|
case arrayValue: return IKV_ARRAY;
|
|
default: return IKV_NULL;
|
|
}
|
|
}
|
|
|
|
ValueType publicType(ikv_type_t type) noexcept {
|
|
switch (type) {
|
|
case IKV_STRING: return stringValue;
|
|
case IKV_INT: return intValue;
|
|
case IKV_FLOAT: return realValue;
|
|
case IKV_BOOL: return booleanValue;
|
|
case IKV_OBJECT: return objectValue;
|
|
case IKV_ARRAY: return arrayValue;
|
|
default: return nullValue;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void Value::Deleter::operator()(ikv_node_t* node) const noexcept { ikv_free(node); }
|
|
|
|
Value::Value(ValueType type, std::string root_name) {
|
|
ikv_node_t* node = nullptr;
|
|
if (type == objectValue) node = ikv_create_object(root_name.c_str());
|
|
else if (type == arrayValue) node = ikv_create_array(root_name.c_str(), IKV_NULL);
|
|
else throw Error("iKv roots must be objects or arrays");
|
|
if (!node) throw Error("failed to allocate iKv root");
|
|
owner_.reset(node, Deleter{});
|
|
node_ = node;
|
|
}
|
|
|
|
Value::Value(Owner owner, ikv_node_t* node, ikv_node_t* parent, std::string key, bool read_only)
|
|
: owner_(std::move(owner)), node_(node), parent_(parent), key_(std::move(key)), read_only_(read_only) {}
|
|
|
|
Value Value::adopt(ikv_node_t* node) {
|
|
if (!node) throw Error("failed to parse iKv data");
|
|
Owner owner(node, Deleter{});
|
|
return Value(owner, node, nullptr, {});
|
|
}
|
|
|
|
Value Value::parse(const std::string& text) { return adopt(ikv_parse_string(text.c_str())); }
|
|
Value Value::load(const std::string& path) { return adopt(ikv_parse_file(path.c_str())); }
|
|
Value Value::fromBinary(const void* data, std::size_t size) {
|
|
if (!data && size != 0) throw Error("binary input is null");
|
|
return adopt(ikvb_parse_memory(data, size));
|
|
}
|
|
Value Value::loadBinary(const std::string& path) { return adopt(ikvb_parse_file(path.c_str())); }
|
|
|
|
ValueType Value::type() const noexcept { return node_ ? publicType(ikv_node_type(node_)) : nullValue; }
|
|
bool Value::isNull() const noexcept { return node_ == nullptr; }
|
|
bool Value::isString() const noexcept { return type() == stringValue; }
|
|
bool Value::isInt() const noexcept { return type() == intValue; }
|
|
bool Value::isDouble() const noexcept { return type() == realValue; }
|
|
bool Value::isBool() const noexcept { return type() == booleanValue; }
|
|
bool Value::isObject() const noexcept { return type() == objectValue; }
|
|
bool Value::isArray() const noexcept { return type() == arrayValue; }
|
|
bool Value::empty() const { return size() == 0; }
|
|
|
|
std::size_t Value::size() const {
|
|
if (isObject()) return ikv_object_size(node_);
|
|
if (isArray()) return ikv_array_size(node_);
|
|
return 0;
|
|
}
|
|
|
|
std::string Value::name() const {
|
|
const char* key = node_ ? ikv_node_key(node_) : nullptr;
|
|
return key ? key : "";
|
|
}
|
|
|
|
bool Value::isMember(const std::string& key) const {
|
|
return isObject() && ikv_object_get(node_, key.c_str()) != nullptr;
|
|
}
|
|
|
|
Value Value::operator[](const std::string& key) {
|
|
require(objectValue, "object lookup");
|
|
return Value(owner_, ikv_object_get(node_, key.c_str()), node_, key);
|
|
}
|
|
|
|
Value Value::operator[](const char* key) {
|
|
if (!key) throw Error("object key is null");
|
|
return (*this)[std::string(key)];
|
|
}
|
|
|
|
Value Value::operator[](const std::string& key) const {
|
|
require(objectValue, "object lookup");
|
|
return Value(owner_, ikv_object_get(node_, key.c_str()), nullptr, {}, true);
|
|
}
|
|
|
|
Value Value::operator[](const char* key) const {
|
|
if (!key) throw Error("object key is null");
|
|
return (*this)[std::string(key)];
|
|
}
|
|
|
|
Value Value::operator[](ArrayIndex index) {
|
|
require(arrayValue, "array lookup");
|
|
if (index >= ikv_array_size(node_)) throw std::out_of_range("iKv array index out of range");
|
|
return Value(owner_, ikv_array_get(node_, index), node_, {});
|
|
}
|
|
|
|
Value Value::operator[](int index) {
|
|
if (index < 0) throw std::out_of_range("iKv array index out of range");
|
|
return (*this)[static_cast<ArrayIndex>(index)];
|
|
}
|
|
|
|
Value Value::operator[](ArrayIndex index) const {
|
|
require(arrayValue, "array lookup");
|
|
if (index >= ikv_array_size(node_)) throw std::out_of_range("iKv array index out of range");
|
|
return Value(owner_, ikv_array_get(node_, index), node_, {}, true);
|
|
}
|
|
|
|
Value Value::operator[](int index) const {
|
|
if (index < 0) throw std::out_of_range("iKv array index out of range");
|
|
return (*this)[static_cast<ArrayIndex>(index)];
|
|
}
|
|
|
|
Value Value::get(const std::string& key, const Value& fallback) const {
|
|
require(objectValue, "object lookup");
|
|
ikv_node_t* child = ikv_object_get(node_, key.c_str());
|
|
return child ? Value(owner_, child, nullptr, {}, true) : fallback;
|
|
}
|
|
|
|
void Value::require(ValueType expected, const char* operation) const {
|
|
if (!node_ || type() != expected) throw Error(std::string(operation) + ": incompatible value type");
|
|
}
|
|
|
|
void Value::requireMutable(const char* operation) const {
|
|
if (read_only_) throw Error(std::string(operation) + ": value is read-only");
|
|
}
|
|
|
|
void Value::requireObjectParent(const char* operation) const {
|
|
requireMutable(operation);
|
|
if (!parent_ || ikv_node_type(parent_) != IKV_OBJECT || key_.empty())
|
|
throw Error(std::string(operation) + ": assignment requires an object member");
|
|
}
|
|
|
|
Value& Value::operator=(const std::string& value) {
|
|
requireObjectParent("string assignment");
|
|
ikv_object_set_string(parent_, key_.c_str(), value.c_str());
|
|
node_ = ikv_object_get(parent_, key_.c_str());
|
|
if (!node_) throw Error("string assignment failed");
|
|
return *this;
|
|
}
|
|
Value& Value::operator=(const char* value) {
|
|
if (!value) throw Error("string value is null");
|
|
return *this = std::string(value);
|
|
}
|
|
Value& Value::operator=(std::int64_t value) {
|
|
requireObjectParent("integer assignment");
|
|
ikv_object_set_int(parent_, key_.c_str(), value);
|
|
node_ = ikv_object_get(parent_, key_.c_str());
|
|
if (!node_) throw Error("integer assignment failed");
|
|
return *this;
|
|
}
|
|
Value& Value::operator=(int value) { return *this = static_cast<std::int64_t>(value); }
|
|
Value& Value::operator=(double value) {
|
|
requireObjectParent("real assignment");
|
|
ikv_object_set_float(parent_, key_.c_str(), value);
|
|
node_ = ikv_object_get(parent_, key_.c_str());
|
|
if (!node_) throw Error("real assignment failed");
|
|
return *this;
|
|
}
|
|
Value& Value::operator=(bool value) {
|
|
requireObjectParent("boolean assignment");
|
|
ikv_object_set_bool(parent_, key_.c_str(), value);
|
|
node_ = ikv_object_get(parent_, key_.c_str());
|
|
if (!node_) throw Error("boolean assignment failed");
|
|
return *this;
|
|
}
|
|
|
|
Value Value::makeObject(const std::string& key) {
|
|
requireMutable("object creation");
|
|
require(objectValue, "object creation");
|
|
ikv_node_t* child = ikv_object_add_object(node_, key.c_str());
|
|
if (!child) throw Error("failed to create object member");
|
|
return Value(owner_, child, node_, key);
|
|
}
|
|
|
|
Value Value::makeArray(const std::string& key, ValueType element_type) {
|
|
requireMutable("array creation");
|
|
require(objectValue, "array creation");
|
|
if (element_type == arrayValue) throw Error("iKv does not support arrays of arrays");
|
|
ikv_node_t* child = ikv_object_add_array(node_, key.c_str(), nativeType(element_type));
|
|
if (!child) throw Error("failed to create array member");
|
|
return Value(owner_, child, node_, key);
|
|
}
|
|
|
|
Value Value::appended(std::size_t old_size) {
|
|
if (ikv_array_size(node_) != old_size + 1) throw Error("array append rejected by its element type");
|
|
return Value(owner_, ikv_array_get(node_, static_cast<std::uint32_t>(old_size)), node_, {});
|
|
}
|
|
Value Value::append(const std::string& value) { requireMutable("append"); require(arrayValue, "append"); auto n=size(); ikv_array_add_string(node_,value.c_str()); return appended(n); }
|
|
Value Value::append(const char* value) { if(!value) throw Error("string value is null"); return append(std::string(value)); }
|
|
Value Value::append(std::int64_t value) { requireMutable("append"); require(arrayValue, "append"); auto n=size(); ikv_array_add_int(node_,value); return appended(n); }
|
|
Value Value::append(int value) { return append(static_cast<std::int64_t>(value)); }
|
|
Value Value::append(double value) { requireMutable("append"); require(arrayValue, "append"); auto n=size(); ikv_array_add_float(node_,value); return appended(n); }
|
|
Value Value::append(bool value) { requireMutable("append"); require(arrayValue, "append"); auto n=size(); ikv_array_add_bool(node_,value); return appended(n); }
|
|
Value Value::appendObject() { requireMutable("append object"); require(arrayValue, "append object"); auto n=size(); if(!ikv_array_add_object(node_)) throw Error("object append rejected by array type"); return appended(n); }
|
|
|
|
std::string Value::asString() const { require(stringValue, "asString"); const char* s=ikv_as_string(node_); return s?s:""; }
|
|
std::int64_t Value::asInt64() const { require(intValue, "asInt64"); return ikv_as_int(node_); }
|
|
int Value::asInt() const {
|
|
auto value = asInt64();
|
|
if (value < std::numeric_limits<int>::min() || value > std::numeric_limits<int>::max())
|
|
throw std::out_of_range("iKv integer does not fit in int");
|
|
return static_cast<int>(value);
|
|
}
|
|
double Value::asDouble() const { require(realValue, "asDouble"); return ikv_as_float(node_); }
|
|
bool Value::asBool() const { require(booleanValue, "asBool"); return ikv_as_bool(node_); }
|
|
|
|
void Value::write(const std::string& path, Version version) const {
|
|
if (!node_ || !ikv_write_file_version(path.c_str(), node_, nativeVersion(version))) throw Error("failed to write iKv file: " + path);
|
|
}
|
|
void Value::writeBinary(const std::string& path, Version version) const {
|
|
if (!node_ || !ikvb_write_file_version(path.c_str(), node_, nativeVersion(version))) throw Error("failed to write binary iKv file: " + path);
|
|
}
|
|
std::vector<std::uint8_t> Value::toBinary(Version version) const {
|
|
std::uint8_t* data = nullptr; std::uint32_t size = 0;
|
|
if (!node_ || !ikvb_write_memory_version(node_, &data, &size, nativeVersion(version))) throw Error("failed to encode binary iKv data");
|
|
std::unique_ptr<std::uint8_t, decltype(&std::free)> memory(data, &std::free);
|
|
std::vector<std::uint8_t> result;
|
|
if (size != 0) result.assign(data, data + size);
|
|
return result;
|
|
}
|
|
void Value::refresh(const std::string& path) {
|
|
requireMutable("refresh");
|
|
if (!owner_ || node_ != owner_.get()) throw Error("refresh is only valid on a root value");
|
|
if (!ikv_refresh_from_path(node_, path.c_str())) throw Error("failed to refresh iKv root from: " + path);
|
|
}
|
|
|
|
} // namespace ikv
|